@psiclawops/hypermem 0.5.0 → 0.5.1

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 (160) hide show
  1. package/dist/background-indexer.d.ts +132 -0
  2. package/dist/background-indexer.d.ts.map +1 -0
  3. package/dist/background-indexer.js +1044 -0
  4. package/dist/cache.d.ts +110 -0
  5. package/dist/cache.d.ts.map +1 -0
  6. package/dist/cache.js +495 -0
  7. package/dist/compaction-fence.d.ts +89 -0
  8. package/dist/compaction-fence.d.ts.map +1 -0
  9. package/dist/compaction-fence.js +153 -0
  10. package/dist/compositor.d.ts +226 -0
  11. package/dist/compositor.d.ts.map +1 -0
  12. package/dist/compositor.js +2558 -0
  13. package/dist/content-type-classifier.d.ts +41 -0
  14. package/dist/content-type-classifier.d.ts.map +1 -0
  15. package/dist/content-type-classifier.js +181 -0
  16. package/dist/cross-agent.d.ts +62 -0
  17. package/dist/cross-agent.d.ts.map +1 -0
  18. package/dist/cross-agent.js +259 -0
  19. package/dist/db.d.ts +131 -0
  20. package/dist/db.d.ts.map +1 -0
  21. package/dist/db.js +402 -0
  22. package/dist/desired-state-store.d.ts +100 -0
  23. package/dist/desired-state-store.d.ts.map +1 -0
  24. package/dist/desired-state-store.js +222 -0
  25. package/dist/doc-chunk-store.d.ts +140 -0
  26. package/dist/doc-chunk-store.d.ts.map +1 -0
  27. package/dist/doc-chunk-store.js +391 -0
  28. package/dist/doc-chunker.d.ts +99 -0
  29. package/dist/doc-chunker.d.ts.map +1 -0
  30. package/dist/doc-chunker.js +324 -0
  31. package/dist/dreaming-promoter.d.ts +86 -0
  32. package/dist/dreaming-promoter.d.ts.map +1 -0
  33. package/dist/dreaming-promoter.js +381 -0
  34. package/dist/episode-store.d.ts +49 -0
  35. package/dist/episode-store.d.ts.map +1 -0
  36. package/dist/episode-store.js +135 -0
  37. package/dist/fact-store.d.ts +75 -0
  38. package/dist/fact-store.d.ts.map +1 -0
  39. package/dist/fact-store.js +236 -0
  40. package/dist/fleet-store.d.ts +144 -0
  41. package/dist/fleet-store.d.ts.map +1 -0
  42. package/dist/fleet-store.js +276 -0
  43. package/dist/fos-mod.d.ts +178 -0
  44. package/dist/fos-mod.d.ts.map +1 -0
  45. package/dist/fos-mod.js +416 -0
  46. package/dist/hybrid-retrieval.d.ts +64 -0
  47. package/dist/hybrid-retrieval.d.ts.map +1 -0
  48. package/dist/hybrid-retrieval.js +344 -0
  49. package/dist/image-eviction.d.ts +49 -0
  50. package/dist/image-eviction.d.ts.map +1 -0
  51. package/dist/image-eviction.js +251 -0
  52. package/dist/index.d.ts +650 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +1072 -0
  55. package/dist/keystone-scorer.d.ts +51 -0
  56. package/dist/keystone-scorer.d.ts.map +1 -0
  57. package/dist/keystone-scorer.js +52 -0
  58. package/dist/knowledge-graph.d.ts +110 -0
  59. package/dist/knowledge-graph.d.ts.map +1 -0
  60. package/dist/knowledge-graph.js +305 -0
  61. package/dist/knowledge-lint.d.ts +29 -0
  62. package/dist/knowledge-lint.d.ts.map +1 -0
  63. package/dist/knowledge-lint.js +116 -0
  64. package/dist/knowledge-store.d.ts +72 -0
  65. package/dist/knowledge-store.d.ts.map +1 -0
  66. package/dist/knowledge-store.js +247 -0
  67. package/dist/library-schema.d.ts +22 -0
  68. package/dist/library-schema.d.ts.map +1 -0
  69. package/dist/library-schema.js +1038 -0
  70. package/dist/message-store.d.ts +89 -0
  71. package/dist/message-store.d.ts.map +1 -0
  72. package/dist/message-store.js +323 -0
  73. package/dist/metrics-dashboard.d.ts +114 -0
  74. package/dist/metrics-dashboard.d.ts.map +1 -0
  75. package/dist/metrics-dashboard.js +260 -0
  76. package/dist/obsidian-exporter.d.ts +57 -0
  77. package/dist/obsidian-exporter.d.ts.map +1 -0
  78. package/dist/obsidian-exporter.js +274 -0
  79. package/dist/obsidian-watcher.d.ts +147 -0
  80. package/dist/obsidian-watcher.d.ts.map +1 -0
  81. package/dist/obsidian-watcher.js +403 -0
  82. package/dist/open-domain.d.ts +46 -0
  83. package/dist/open-domain.d.ts.map +1 -0
  84. package/dist/open-domain.js +125 -0
  85. package/dist/preference-store.d.ts +54 -0
  86. package/dist/preference-store.d.ts.map +1 -0
  87. package/dist/preference-store.js +109 -0
  88. package/dist/preservation-gate.d.ts +82 -0
  89. package/dist/preservation-gate.d.ts.map +1 -0
  90. package/dist/preservation-gate.js +150 -0
  91. package/dist/proactive-pass.d.ts +63 -0
  92. package/dist/proactive-pass.d.ts.map +1 -0
  93. package/dist/proactive-pass.js +239 -0
  94. package/dist/profiles.d.ts +44 -0
  95. package/dist/profiles.d.ts.map +1 -0
  96. package/dist/profiles.js +227 -0
  97. package/dist/provider-translator.d.ts +50 -0
  98. package/dist/provider-translator.d.ts.map +1 -0
  99. package/dist/provider-translator.js +403 -0
  100. package/dist/rate-limiter.d.ts +76 -0
  101. package/dist/rate-limiter.d.ts.map +1 -0
  102. package/dist/rate-limiter.js +179 -0
  103. package/dist/repair-tool-pairs.d.ts +38 -0
  104. package/dist/repair-tool-pairs.d.ts.map +1 -0
  105. package/dist/repair-tool-pairs.js +138 -0
  106. package/dist/retrieval-policy.d.ts +51 -0
  107. package/dist/retrieval-policy.d.ts.map +1 -0
  108. package/dist/retrieval-policy.js +77 -0
  109. package/dist/schema.d.ts +15 -0
  110. package/dist/schema.d.ts.map +1 -0
  111. package/dist/schema.js +229 -0
  112. package/dist/secret-scanner.d.ts +51 -0
  113. package/dist/secret-scanner.d.ts.map +1 -0
  114. package/dist/secret-scanner.js +248 -0
  115. package/dist/seed.d.ts +108 -0
  116. package/dist/seed.d.ts.map +1 -0
  117. package/dist/seed.js +177 -0
  118. package/dist/session-flusher.d.ts +53 -0
  119. package/dist/session-flusher.d.ts.map +1 -0
  120. package/dist/session-flusher.js +69 -0
  121. package/dist/session-topic-map.d.ts +41 -0
  122. package/dist/session-topic-map.d.ts.map +1 -0
  123. package/dist/session-topic-map.js +77 -0
  124. package/dist/spawn-context.d.ts +54 -0
  125. package/dist/spawn-context.d.ts.map +1 -0
  126. package/dist/spawn-context.js +159 -0
  127. package/dist/system-store.d.ts +73 -0
  128. package/dist/system-store.d.ts.map +1 -0
  129. package/dist/system-store.js +182 -0
  130. package/dist/temporal-store.d.ts +80 -0
  131. package/dist/temporal-store.d.ts.map +1 -0
  132. package/dist/temporal-store.js +149 -0
  133. package/dist/topic-detector.d.ts +35 -0
  134. package/dist/topic-detector.d.ts.map +1 -0
  135. package/dist/topic-detector.js +249 -0
  136. package/dist/topic-store.d.ts +45 -0
  137. package/dist/topic-store.d.ts.map +1 -0
  138. package/dist/topic-store.js +136 -0
  139. package/dist/topic-synthesizer.d.ts +51 -0
  140. package/dist/topic-synthesizer.d.ts.map +1 -0
  141. package/dist/topic-synthesizer.js +315 -0
  142. package/dist/trigger-registry.d.ts +63 -0
  143. package/dist/trigger-registry.d.ts.map +1 -0
  144. package/dist/trigger-registry.js +163 -0
  145. package/dist/types.d.ts +533 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +9 -0
  148. package/dist/vector-store.d.ts +170 -0
  149. package/dist/vector-store.d.ts.map +1 -0
  150. package/dist/vector-store.js +677 -0
  151. package/dist/version.d.ts +34 -0
  152. package/dist/version.d.ts.map +1 -0
  153. package/dist/version.js +34 -0
  154. package/dist/wiki-page-emitter.d.ts +65 -0
  155. package/dist/wiki-page-emitter.d.ts.map +1 -0
  156. package/dist/wiki-page-emitter.js +258 -0
  157. package/dist/work-store.d.ts +112 -0
  158. package/dist/work-store.d.ts.map +1 -0
  159. package/dist/work-store.js +273 -0
  160. package/package.json +1 -1
@@ -0,0 +1,178 @@
1
+ /**
2
+ * hypermem FOS/MOD — Fleet Output Standard & Model Output Directives
3
+ *
4
+ * Provides per-model output calibration injected into the context window.
5
+ * Thread-safe: no module-scoped state. All functions take explicit db parameter.
6
+ *
7
+ * FOS (Fleet Output Standard): shared rules applied to all agents.
8
+ * MOD (Model Output Directive): per-model corrections and calibrations.
9
+ */
10
+ import type { DatabaseSync } from 'node:sqlite';
11
+ export interface FOSDirectives {
12
+ structural?: string[];
13
+ anti_patterns?: string[];
14
+ density_targets?: Record<string, string>;
15
+ voice?: string[];
16
+ }
17
+ export interface FOSTaskVariant {
18
+ density_target?: string;
19
+ structure?: string;
20
+ list_cap?: string;
21
+ }
22
+ export interface FOSRecord {
23
+ id: string;
24
+ name: string;
25
+ directives: FOSDirectives;
26
+ task_variants: Record<string, FOSTaskVariant>;
27
+ token_budget: number;
28
+ active: number;
29
+ source: string;
30
+ version: number;
31
+ last_validated_at: string | null;
32
+ created_at: string;
33
+ updated_at: string;
34
+ }
35
+ export interface MODCorrection {
36
+ id: string;
37
+ rule: string;
38
+ severity: 'hard' | 'medium' | 'soft';
39
+ }
40
+ export interface MODCalibration {
41
+ id: string;
42
+ fos_target: string;
43
+ model_tendency: string;
44
+ adjustment: string;
45
+ }
46
+ export interface MODRecord {
47
+ id: string;
48
+ match_pattern: string;
49
+ priority: number;
50
+ corrections: MODCorrection[];
51
+ calibration: MODCalibration[];
52
+ task_overrides: Record<string, unknown>;
53
+ token_budget: number;
54
+ version: number;
55
+ source: string;
56
+ enabled: number;
57
+ last_validated_at: string | null;
58
+ created_at: string;
59
+ updated_at: string;
60
+ }
61
+ export interface OutputMetricsRow {
62
+ id: string;
63
+ timestamp: string;
64
+ agent_id: string;
65
+ session_key: string;
66
+ model_id: string;
67
+ provider: string;
68
+ fos_version?: number | null;
69
+ mod_version?: number | null;
70
+ mod_id?: string | null;
71
+ task_type?: string | null;
72
+ output_tokens: number;
73
+ input_tokens?: number | null;
74
+ cache_read_tokens?: number | null;
75
+ corrections_fired?: string[];
76
+ latency_ms?: number | null;
77
+ }
78
+ /**
79
+ * Get the active FOS profile.
80
+ * Returns null if no active profile exists or tables don't exist yet.
81
+ */
82
+ export declare function getActiveFOS(db: DatabaseSync): FOSRecord | null;
83
+ /**
84
+ * Match a MOD for the given model ID.
85
+ *
86
+ * Match hierarchy (in order):
87
+ * 1. Exact match on id (case-sensitive)
88
+ * 2. Glob pattern match — longest pattern wins ties
89
+ * 3. Wildcard '*' fallback
90
+ * 4. null
91
+ *
92
+ * Only enabled=1 MODs are considered. Higher priority wins on equal pattern length.
93
+ */
94
+ export declare function matchMOD(modelId: string | undefined, db: DatabaseSync): MODRecord | null;
95
+ /**
96
+ * Render a FOS record into prompt lines.
97
+ *
98
+ * Output format:
99
+ * ## Output Standard (Fleet)
100
+ * - Lead with the answer...
101
+ * Never: no em dashes, no sycophancy...
102
+ * Simple: 1-3 sentences. Analysis: 200-500 words. Code: code first.
103
+ * - Numbers over adjectives...
104
+ *
105
+ * Respects token_budget (default 250 tokens). Never cuts mid-sentence.
106
+ * If taskContext is provided and a matching task variant exists, overrides density targets.
107
+ */
108
+ /**
109
+ * Output standard tiers. Controls what FOS content is injected into context.
110
+ *
111
+ * 'light' — ~100 token standalone directives. No MOD, no fleet concepts.
112
+ * Works on any single-agent 64k setup. No DB required.
113
+ * 'standard' — Full FOS: density targets, format rules, compression ratios,
114
+ * task-context scoping. MOD suppressed.
115
+ * 'full' — FOS + MOD. Full spec for multi-agent fleet operators.
116
+ *
117
+ * Backward compat: 'starter' maps to 'light', 'fleet' maps to 'full'.
118
+ */
119
+ export type OutputProfileTier = 'light' | 'standard' | 'full';
120
+ /** @deprecated Use OutputProfileTier */
121
+ export type OutputStandardTier = OutputProfileTier;
122
+ /**
123
+ * Render the light-tier output profile.
124
+ * Standalone: no compositor concepts, no fleet terminology, no DB required.
125
+ * ~100 tokens. Covers anti-sycophancy, em dash ban, AI vocab ban, length targets,
126
+ * anti-pagination, and evidence calibration.
127
+ */
128
+ export declare function renderLightFOS(): string[];
129
+ /** @deprecated Use renderLightFOS */
130
+ export declare function renderStarterFOS(): string[];
131
+ /**
132
+ * Resolve the effective output standard tier given compositor config.
133
+ * MOD is only eligible at the 'fleet' tier.
134
+ */
135
+ export declare function resolveOutputTier(outputProfile: OutputProfileTier | undefined, enableFOS: boolean | undefined, enableMOD: boolean | undefined): {
136
+ tier: OutputProfileTier;
137
+ fos: boolean;
138
+ mod: boolean;
139
+ };
140
+ export declare function renderFOS(fos: FOSRecord, taskContext?: string): string[];
141
+ /**
142
+ * Render a MOD record into prompt lines.
143
+ *
144
+ * Output format:
145
+ * ## Output Calibration (gpt-5.4)
146
+ * Known tendencies: 2x verbosity, 1.8x list length vs target.
147
+ * - Actively compress. Cut first drafts in half.
148
+ * - Do not open with I. No preamble before the answer.
149
+ *
150
+ * Respects token_budget (default 150 tokens). Never cuts mid-sentence.
151
+ */
152
+ export declare function renderMOD(mod: MODRecord, fos: FOSRecord | null, modelId: string, taskContext?: string): string[];
153
+ export type { NeutralMessage } from './types.js';
154
+ /**
155
+ * Build a rolling summary of the last N verified tool actions from the message window.
156
+ *
157
+ * Scans for tool_use/tool_result pairs (matched by tool_use_id / callId).
158
+ * Renders as:
159
+ * ## Recent Actions
160
+ * - tool_name: result_summary
161
+ * ...
162
+ *
163
+ * Pressure-aware:
164
+ * <80% → 5 actions
165
+ * 80-90% → 3 actions
166
+ * 90-95% → 1 action
167
+ * ≥95% → empty string (drop entirely)
168
+ *
169
+ * Token budget: 150 tokens total.
170
+ * Gate: returns '' when pressurePct >= 95.
171
+ */
172
+ export declare function buildActionVerificationSummary(messages: import('./types.js').NeutralMessage[], pressurePct: number): string;
173
+ /**
174
+ * Record output metrics for analytics.
175
+ * Best-effort: logs errors but never throws.
176
+ */
177
+ export declare function recordOutputMetrics(db: DatabaseSync, metrics: OutputMetricsRow): void;
178
+ //# sourceMappingURL=fos-mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fos-mod.d.ts","sourceRoot":"","sources":["../src/fos-mod.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,416 @@
1
+ /**
2
+ * hypermem FOS/MOD — Fleet Output Standard & Model Output Directives
3
+ *
4
+ * Provides per-model output calibration injected into the context window.
5
+ * Thread-safe: no module-scoped state. All functions take explicit db parameter.
6
+ *
7
+ * FOS (Fleet Output Standard): shared rules applied to all agents.
8
+ * MOD (Model Output Directive): per-model corrections and calibrations.
9
+ */
10
+ // ── Token budget constants ────────────────────────────────────
11
+ const FOS_TOKEN_BUDGET = 250;
12
+ const MOD_TOKEN_BUDGET = 150;
13
+ // ── Internal helpers ──────────────────────────────────────────
14
+ function tryParseJson(val, fallback) {
15
+ if (!val)
16
+ return fallback;
17
+ try {
18
+ return JSON.parse(val);
19
+ }
20
+ catch {
21
+ return fallback;
22
+ }
23
+ }
24
+ /**
25
+ * Rough token estimate: 4 chars per token on average.
26
+ */
27
+ function estimateTokens(text) {
28
+ return Math.ceil(text.length / 4);
29
+ }
30
+ /**
31
+ * Truncate an array of strings to fit within a token budget.
32
+ * Never cuts mid-item: drops complete items from the end.
33
+ */
34
+ function truncateLines(lines, budget) {
35
+ const result = [];
36
+ let total = 0;
37
+ for (const line of lines) {
38
+ const cost = estimateTokens(line) + 1; // +1 for newline
39
+ if (total + cost > budget)
40
+ break;
41
+ result.push(line);
42
+ total += cost;
43
+ }
44
+ return result;
45
+ }
46
+ function parseRow(row) {
47
+ return {
48
+ id: row.id,
49
+ name: row.name,
50
+ directives: tryParseJson(row.directives, {}),
51
+ task_variants: tryParseJson(row.task_variants, {}),
52
+ token_budget: row.token_budget ?? FOS_TOKEN_BUDGET,
53
+ active: row.active,
54
+ source: row.source,
55
+ version: row.version,
56
+ last_validated_at: row.last_validated_at || null,
57
+ created_at: row.created_at,
58
+ updated_at: row.updated_at,
59
+ };
60
+ }
61
+ function parseMODRow(row) {
62
+ return {
63
+ id: row.id,
64
+ match_pattern: row.match_pattern,
65
+ priority: row.priority ?? 0,
66
+ corrections: tryParseJson(row.corrections, []),
67
+ calibration: tryParseJson(row.calibration, []),
68
+ task_overrides: tryParseJson(row.task_overrides, {}),
69
+ token_budget: row.token_budget ?? MOD_TOKEN_BUDGET,
70
+ version: row.version ?? 1,
71
+ source: row.source || 'builtin',
72
+ enabled: row.enabled ?? 1,
73
+ last_validated_at: row.last_validated_at || null,
74
+ created_at: row.created_at,
75
+ updated_at: row.updated_at,
76
+ };
77
+ }
78
+ // ── Core API ──────────────────────────────────────────────────
79
+ /**
80
+ * Get the active FOS profile.
81
+ * Returns null if no active profile exists or tables don't exist yet.
82
+ */
83
+ export function getActiveFOS(db) {
84
+ try {
85
+ const row = db.prepare("SELECT * FROM fleet_output_standard WHERE active = 1 ORDER BY version DESC LIMIT 1").get();
86
+ return row ? parseRow(row) : null;
87
+ }
88
+ catch {
89
+ // Table doesn't exist yet (pre-migration)
90
+ return null;
91
+ }
92
+ }
93
+ /**
94
+ * Match a MOD for the given model ID.
95
+ *
96
+ * Match hierarchy (in order):
97
+ * 1. Exact match on id (case-sensitive)
98
+ * 2. Glob pattern match — longest pattern wins ties
99
+ * 3. Wildcard '*' fallback
100
+ * 4. null
101
+ *
102
+ * Only enabled=1 MODs are considered. Higher priority wins on equal pattern length.
103
+ */
104
+ export function matchMOD(modelId, db) {
105
+ if (!modelId)
106
+ return null;
107
+ try {
108
+ const rows = db.prepare("SELECT * FROM model_output_directives WHERE enabled = 1 ORDER BY priority DESC, id ASC").all();
109
+ if (rows.length === 0)
110
+ return null;
111
+ const mods = rows.map(parseMODRow);
112
+ const model = modelId.toLowerCase();
113
+ // 1. Exact match on id
114
+ for (const mod of mods) {
115
+ if (mod.id.toLowerCase() === model)
116
+ return mod;
117
+ }
118
+ // 2. Glob match — collect all matching patterns, pick longest
119
+ // Supports: prefix* (e.g., 'gpt-5.4*'), *suffix, *wildcard*
120
+ const globMatches = [];
121
+ for (const mod of mods) {
122
+ const pattern = mod.match_pattern;
123
+ if (pattern === '*')
124
+ continue; // wildcard handled separately
125
+ if (globMatch(pattern, modelId)) {
126
+ globMatches.push({ mod, patternLen: pattern.length });
127
+ }
128
+ }
129
+ if (globMatches.length > 0) {
130
+ // Sort by pattern length descending (longer = more specific), then priority descending
131
+ globMatches.sort((a, b) => {
132
+ if (b.patternLen !== a.patternLen)
133
+ return b.patternLen - a.patternLen;
134
+ return b.mod.priority - a.mod.priority;
135
+ });
136
+ return globMatches[0].mod;
137
+ }
138
+ // 3. Wildcard '*' fallback
139
+ for (const mod of mods) {
140
+ if (mod.match_pattern === '*')
141
+ return mod;
142
+ }
143
+ return null;
144
+ }
145
+ catch {
146
+ // Table doesn't exist yet
147
+ return null;
148
+ }
149
+ }
150
+ /**
151
+ * Simple glob matching: supports * as wildcard substring.
152
+ * Pattern like 'gpt-5.4*' matches 'gpt-5.4', 'gpt-5.4-turbo', etc.
153
+ * Case-insensitive.
154
+ */
155
+ function globMatch(pattern, value) {
156
+ const p = pattern.toLowerCase();
157
+ const v = value.toLowerCase();
158
+ if (!p.includes('*'))
159
+ return p === v;
160
+ const parts = p.split('*');
161
+ let pos = 0;
162
+ for (let i = 0; i < parts.length; i++) {
163
+ const part = parts[i];
164
+ if (i === 0) {
165
+ // First segment must match at start
166
+ if (!v.startsWith(part))
167
+ return false;
168
+ pos = part.length;
169
+ }
170
+ else if (i === parts.length - 1) {
171
+ // Last segment must match at end
172
+ if (!v.endsWith(part))
173
+ return false;
174
+ }
175
+ else {
176
+ const idx = v.indexOf(part, pos);
177
+ if (idx === -1)
178
+ return false;
179
+ pos = idx + part.length;
180
+ }
181
+ }
182
+ return true;
183
+ }
184
+ /**
185
+ * Render the light-tier output profile.
186
+ * Standalone: no compositor concepts, no fleet terminology, no DB required.
187
+ * ~100 tokens. Covers anti-sycophancy, em dash ban, AI vocab ban, length targets,
188
+ * anti-pagination, and evidence calibration.
189
+ */
190
+ export function renderLightFOS() {
191
+ return [
192
+ '## Output Standard',
193
+ '- Lead with the answer. Conclusion first, reasoning after.',
194
+ '- No sycophantic openings: never start with "Great question", "Certainly", "Absolutely", "Of course".',
195
+ '- No em dashes. Use commas, colons, semicolons, or periods.',
196
+ '- Avoid AI vocabulary: delve, tapestry, pivotal, fostering, leverage, robust, noteworthy, realm, harness, showcase.',
197
+ '- Simple answers: 1-3 sentences. Analysis: 200-500 words. Code: code first, explain only non-obvious parts.',
198
+ '- Every sentence states a fact, makes a decision, or advances an argument. Cut filler.',
199
+ '- Do not paginate short answers. No headers, sections, or bullet lists unless the content requires structure.',
200
+ '- Numbers over adjectives. Match confidence to evidence.',
201
+ ];
202
+ }
203
+ /** @deprecated Use renderLightFOS */
204
+ export function renderStarterFOS() {
205
+ return renderLightFOS();
206
+ }
207
+ /**
208
+ * Resolve the effective output standard tier given compositor config.
209
+ * MOD is only eligible at the 'fleet' tier.
210
+ */
211
+ export function resolveOutputTier(outputProfile, enableFOS, enableMOD) {
212
+ // Legacy path: if outputProfile is not set, honor enableFOS/enableMOD directly
213
+ if (outputProfile === undefined) {
214
+ return {
215
+ tier: 'full',
216
+ fos: enableFOS !== false,
217
+ mod: enableMOD !== false,
218
+ };
219
+ }
220
+ // Backward compat: map old names to new
221
+ const tier = outputProfile === 'starter' ? 'light'
222
+ : outputProfile === 'fleet' ? 'full'
223
+ : outputProfile;
224
+ switch (tier) {
225
+ case 'light':
226
+ return { tier: 'light', fos: false, mod: false };
227
+ case 'standard':
228
+ return { tier: 'standard', fos: true, mod: false };
229
+ case 'full':
230
+ return { tier: 'full', fos: true, mod: enableMOD !== false };
231
+ default:
232
+ return { tier: 'full', fos: true, mod: enableMOD !== false };
233
+ }
234
+ }
235
+ export function renderFOS(fos, taskContext) {
236
+ const budget = fos.token_budget || FOS_TOKEN_BUDGET;
237
+ const d = fos.directives;
238
+ const lines = ['## Output Standard (Fleet)'];
239
+ // Structural rules
240
+ if (d.structural?.length) {
241
+ for (const rule of d.structural) {
242
+ lines.push(`- ${rule}`);
243
+ }
244
+ }
245
+ // Anti-patterns — condense into one line for density
246
+ if (d.anti_patterns?.length) {
247
+ lines.push(`Never: ${d.anti_patterns.join('; ')}`);
248
+ }
249
+ // Density targets — check for task variant override first
250
+ const variant = taskContext ? fos.task_variants[taskContext] : undefined;
251
+ if (variant) {
252
+ if (variant.density_target) {
253
+ lines.push(`Density: ${variant.density_target}`);
254
+ }
255
+ if (variant.structure) {
256
+ lines.push(`Structure: ${variant.structure}`);
257
+ }
258
+ if (variant.list_cap) {
259
+ lines.push(`List cap: ${variant.list_cap}`);
260
+ }
261
+ }
262
+ else if (d.density_targets) {
263
+ const parts = Object.entries(d.density_targets)
264
+ .map(([k, v]) => `${k[0].toUpperCase() + k.slice(1)}: ${v}`)
265
+ .join('. ');
266
+ if (parts)
267
+ lines.push(parts + '.');
268
+ }
269
+ // Voice rules
270
+ if (d.voice?.length) {
271
+ for (const rule of d.voice) {
272
+ lines.push(`- ${rule}`);
273
+ }
274
+ }
275
+ return truncateLines(lines, budget);
276
+ }
277
+ /**
278
+ * Render a MOD record into prompt lines.
279
+ *
280
+ * Output format:
281
+ * ## Output Calibration (gpt-5.4)
282
+ * Known tendencies: 2x verbosity, 1.8x list length vs target.
283
+ * - Actively compress. Cut first drafts in half.
284
+ * - Do not open with I. No preamble before the answer.
285
+ *
286
+ * Respects token_budget (default 150 tokens). Never cuts mid-sentence.
287
+ */
288
+ export function renderMOD(mod, fos, modelId, taskContext) {
289
+ const budget = mod.token_budget || MOD_TOKEN_BUDGET;
290
+ const lines = [`## Output Calibration (${modelId})`];
291
+ // Calibration summary first (tendencies are the "known state")
292
+ if (mod.calibration.length > 0) {
293
+ const tendencies = mod.calibration.map(c => c.model_tendency).join(', ');
294
+ lines.push(`Known tendencies: ${tendencies}`);
295
+ }
296
+ // Calibration adjustments
297
+ for (const cal of mod.calibration) {
298
+ if (cal.adjustment) {
299
+ lines.push(`- ${cal.adjustment}`);
300
+ }
301
+ }
302
+ // Corrections — hard severity first, then medium
303
+ const sorted = [...mod.corrections].sort((a, b) => {
304
+ const order = { hard: 0, medium: 1, soft: 2 };
305
+ return (order[a.severity] ?? 2) - (order[b.severity] ?? 2);
306
+ });
307
+ for (const correction of sorted) {
308
+ lines.push(`- ${correction.rule}`);
309
+ }
310
+ // Task-specific overrides (if taskContext provided and overrides exist)
311
+ if (taskContext && mod.task_overrides[taskContext]) {
312
+ const override = mod.task_overrides[taskContext];
313
+ for (const [, v] of Object.entries(override)) {
314
+ lines.push(`- [${taskContext}] ${v}`);
315
+ }
316
+ }
317
+ void fos; // fos reserved for future cross-calibration
318
+ return truncateLines(lines, budget);
319
+ }
320
+ /**
321
+ * Build a rolling summary of the last N verified tool actions from the message window.
322
+ *
323
+ * Scans for tool_use/tool_result pairs (matched by tool_use_id / callId).
324
+ * Renders as:
325
+ * ## Recent Actions
326
+ * - tool_name: result_summary
327
+ * ...
328
+ *
329
+ * Pressure-aware:
330
+ * <80% → 5 actions
331
+ * 80-90% → 3 actions
332
+ * 90-95% → 1 action
333
+ * ≥95% → empty string (drop entirely)
334
+ *
335
+ * Token budget: 150 tokens total.
336
+ * Gate: returns '' when pressurePct >= 95.
337
+ */
338
+ export function buildActionVerificationSummary(messages, pressurePct) {
339
+ if (pressurePct >= 95)
340
+ return '';
341
+ const maxActions = pressurePct < 80 ? 5 :
342
+ pressurePct < 90 ? 3 : 1;
343
+ // Build an index of tool_use → result by scanning all messages
344
+ // tool_use: messages with toolCalls
345
+ // tool_result: messages with toolResults
346
+ // Match by tool_use_id (NeutralToolCall.id === NeutralToolResult.callId)
347
+ // Collect all tool_use entries in order (newest first)
348
+ const toolPairs = [];
349
+ // Walk newest to oldest to get the most recent actions first
350
+ for (let i = messages.length - 1; i >= 0 && toolPairs.length < maxActions; i--) {
351
+ const msg = messages[i];
352
+ if (!msg.toolCalls || msg.toolCalls.length === 0)
353
+ continue;
354
+ for (const tc of msg.toolCalls) {
355
+ if (toolPairs.length >= maxActions)
356
+ break;
357
+ // Find the matching tool_result by callId
358
+ let resultContent;
359
+ // Search forward from this message for the result
360
+ for (let j = i; j < messages.length; j++) {
361
+ const resultMsg = messages[j];
362
+ if (!resultMsg.toolResults)
363
+ continue;
364
+ const match = resultMsg.toolResults.find(r => r.callId === tc.id);
365
+ if (match) {
366
+ resultContent = match.content ?? '';
367
+ break;
368
+ }
369
+ }
370
+ // Only include verified pairs (tool_use + matching tool_result)
371
+ if (resultContent === undefined)
372
+ continue;
373
+ // Truncate result to 100 chars
374
+ const raw = resultContent.replace(/\s+/g, ' ').trim();
375
+ const summary = raw.length > 100 ? raw.slice(0, 100) + '\u2026' : raw;
376
+ toolPairs.push({ name: tc.name, resultSummary: summary });
377
+ }
378
+ }
379
+ if (toolPairs.length === 0)
380
+ return '';
381
+ const TOKEN_BUDGET = 150;
382
+ const lines = ['## Recent Actions'];
383
+ for (const pair of toolPairs) {
384
+ const line = `- ${pair.name}: ${pair.resultSummary}`;
385
+ const currentCost = estimateTokens(lines.join('\n') + '\n' + line);
386
+ if (currentCost > TOKEN_BUDGET)
387
+ break;
388
+ lines.push(line);
389
+ }
390
+ // If only the header was added (no pairs fit), return empty
391
+ if (lines.length <= 1)
392
+ return '';
393
+ return lines.join('\n');
394
+ }
395
+ /**
396
+ * Record output metrics for analytics.
397
+ * Best-effort: logs errors but never throws.
398
+ */
399
+ export function recordOutputMetrics(db, metrics) {
400
+ try {
401
+ const now = new Date().toISOString();
402
+ db.prepare(`
403
+ INSERT OR REPLACE INTO output_metrics (
404
+ id, timestamp, agent_id, session_key, model_id, provider,
405
+ fos_version, mod_version, mod_id, task_type,
406
+ output_tokens, input_tokens, cache_read_tokens,
407
+ corrections_fired, latency_ms, created_at
408
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
409
+ `).run(metrics.id, metrics.timestamp, metrics.agent_id, metrics.session_key, metrics.model_id, metrics.provider, metrics.fos_version ?? null, metrics.mod_version ?? null, metrics.mod_id ?? null, metrics.task_type ?? null, metrics.output_tokens, metrics.input_tokens ?? null, metrics.cache_read_tokens ?? null, JSON.stringify(metrics.corrections_fired ?? []), metrics.latency_ms ?? null, now);
410
+ }
411
+ catch (err) {
412
+ // Non-fatal — metrics are optional
413
+ console.warn('[fos-mod] recordOutputMetrics failed:', err.message);
414
+ }
415
+ }
416
+ //# sourceMappingURL=fos-mod.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * hypermem Hybrid Retrieval — FTS5 + KNN Score Fusion
3
+ *
4
+ * Merges keyword (FTS5/BM25) and semantic (KNN/vector) results into a
5
+ * single ranked list using Reciprocal Rank Fusion (RRF). This avoids
6
+ * vocabulary mismatch (KNN-only misses exact terms) and semantic gap
7
+ * (FTS5-only misses paraphrases).
8
+ *
9
+ * Architecture:
10
+ * - FTS5 results from library.db (facts_fts, knowledge_fts, episodes_fts)
11
+ * - KNN results from vectors.db via VectorStore
12
+ * - RRF merges both ranked lists with configurable k constant
13
+ * - Deduplication by (sourceTable, sourceId)
14
+ * - Token-budgeted output for compositor consumption
15
+ */
16
+ import type { DatabaseSync } from 'node:sqlite';
17
+ import type { VectorStore } from './vector-store.js';
18
+ export interface HybridSearchResult {
19
+ sourceTable: string;
20
+ sourceId: number;
21
+ content: string;
22
+ domain?: string;
23
+ agentId?: string;
24
+ metadata?: string;
25
+ /** ISO timestamp from source row — used for recency decay (TUNE-015) */
26
+ createdAt?: string;
27
+ /** Combined RRF score (higher = more relevant) */
28
+ score: number;
29
+ /** Which retrieval paths contributed */
30
+ sources: ('fts' | 'knn')[];
31
+ }
32
+ export interface HybridSearchOptions {
33
+ /** Content types to search. Default: ['facts', 'knowledge', 'episodes'] */
34
+ tables?: string[];
35
+ /** Max results to return. Default: 10 */
36
+ limit?: number;
37
+ /** Max KNN distance (filters low-quality vectors). Default: 1.2 */
38
+ maxKnnDistance?: number;
39
+ /** RRF k constant. Higher = less weight to top ranks. Default: 60 */
40
+ rrfK?: number;
41
+ /** Agent ID filter for FTS queries */
42
+ agentId?: string;
43
+ /** Weight for FTS results in fusion. Default: 1.0 */
44
+ ftsWeight?: number;
45
+ /** Weight for KNN results in fusion. Default: 1.0 */
46
+ knnWeight?: number;
47
+ /** Minimum number of FTS terms to attempt a query (skip if fewer). Default: 1 */
48
+ minFtsTerms?: number;
49
+ /** Pre-computed embedding for the query — skips Ollama call in VectorStore.search() */
50
+ precomputedEmbedding?: Float32Array;
51
+ }
52
+ /**
53
+ * Build an FTS5 query from a natural language string.
54
+ * Extracts meaningful words, removes stop words, uses OR conjunction.
55
+ */
56
+ export declare function buildFtsQuery(input: string): string;
57
+ /**
58
+ * Hybrid search combining FTS5 keyword search and KNN vector search.
59
+ *
60
+ * When vectorStore is null, falls back to FTS5-only.
61
+ * When FTS5 query is empty (all stop words), falls back to KNN-only.
62
+ */
63
+ export declare function hybridSearch(libraryDb: DatabaseSync, vectorStore: VectorStore | null, query: string, opts?: HybridSearchOptions): Promise<HybridSearchResult[]>;
64
+ //# sourceMappingURL=hybrid-retrieval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid-retrieval.d.ts","sourceRoot":"","sources":["../src/hybrid-retrieval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,mBAAmB,CAAC;AAIzE,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,OAAO,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,oBAAoB,CAAC,EAAE,YAAY,CAAC;CACrC;AAqBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBnD;AA8ND;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,YAAY,EACvB,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CA0I/B"}