omnius 1.0.591 → 1.0.592

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 (131) hide show
  1. package/.aiwg/addons/omnius-docs/README.md +15 -1
  2. package/.aiwg/addons/omnius-docs/manifest.json +28 -68
  3. package/.aiwg/addons/omnius-docs/skills/agent-failure-recovery/SKILL.md +2 -1
  4. package/.aiwg/addons/omnius-docs/skills/browser-interaction-validation/SKILL.md +2 -1
  5. package/.aiwg/addons/omnius-docs/skills/evidence-directed-delivery/SKILL.md +2 -1
  6. package/.aiwg/addons/omnius-docs/skills/hardware-evidence-audit/SKILL.md +2 -1
  7. package/.aiwg/addons/omnius-docs/skills/omnius-docs/SKILL.md +17 -7
  8. package/.aiwg/addons/omnius-docs/skills/omnius-inference-docs/SKILL.md +27 -0
  9. package/.aiwg/addons/omnius-docs/skills/omnius-integration-docs/SKILL.md +21 -0
  10. package/.aiwg/addons/omnius-docs/skills/omnius-ops-docs/SKILL.md +2 -0
  11. package/.aiwg/addons/omnius-docs/skills/omnius-realtime-docs/SKILL.md +2 -0
  12. package/.aiwg/addons/omnius-docs/skills/omnius-sponsor-docs/SKILL.md +2 -0
  13. package/.aiwg/addons/omnius-docs/skills/omnius-telegram-docs/SKILL.md +2 -0
  14. package/.aiwg/addons/omnius-docs/skills/omnius-tools-docs/SKILL.md +23 -0
  15. package/.aiwg/addons/omnius-docs/skills/omnius-version-compatibility-docs/SKILL.md +23 -0
  16. package/.aiwg/addons/omnius-docs/skills/runtime-provenance-audit/SKILL.md +2 -1
  17. package/.aiwg/addons/omnius-docs/skills/secrets-and-config-audit/SKILL.md +2 -1
  18. package/.aiwg/addons/omnius-docs/skills/test-surface-audit/SKILL.md +2 -1
  19. package/.aiwg/addons/omnius-docs/skills/workspace-reality-audit/SKILL.md +2 -1
  20. package/.aiwg/addons/omnius-rest-docs/README.md +3 -0
  21. package/.aiwg/addons/omnius-rest-docs/manifest.json +27 -20
  22. package/.aiwg/addons/omnius-rest-docs/skills/omnius-rest-docs/SKILL.md +9 -5
  23. package/README.md +36 -0
  24. package/dist/discovery.d.ts +50 -0
  25. package/dist/index.js +5975 -4021
  26. package/dist/library.d.ts +7 -0
  27. package/dist/library.js +950 -0
  28. package/dist/postinstall-daemon.cjs +18 -0
  29. package/dist/providerRegistry.d.ts +80 -0
  30. package/dist/service-version.d.ts +35 -0
  31. package/docs/.vitepress/config.mts +8 -0
  32. package/docs/DISCOVERY.json +20224 -0
  33. package/docs/DISCOVERY.md +648 -0
  34. package/docs/HANDOFF-crl-encoder-decoder-fix.md +129 -0
  35. package/docs/agent-memory/INDEX.md +9 -4
  36. package/docs/agent-memory/index.md +7 -0
  37. package/docs/concept-relational-language.md +869 -0
  38. package/docs/context-management-medium-models-proposal.md +449 -0
  39. package/docs/dedup-false-positive-meta-analysis.md +96 -0
  40. package/docs/discovery/catalog-overrides.json +724 -0
  41. package/docs/duplicate-calls-root-cause-analysis.md +91 -0
  42. package/docs/duplicate-calls-root-cause-deep.md +155 -0
  43. package/docs/ephemeral-skill-pack-small-context.md +57 -0
  44. package/docs/explorations/context-window-todo-association.md +156 -0
  45. package/docs/explorations/todo-association-verify.json +30 -0
  46. package/docs/explorations/verification-ledger.json +45 -0
  47. package/docs/explorations/verify-todo-association.sh +30 -0
  48. package/docs/flowstate.md +806 -0
  49. package/docs/getting-started/install.md +24 -0
  50. package/docs/getting-started/model-providers.md +13 -0
  51. package/docs/guides/agent-integration.md +87 -0
  52. package/docs/guides/bring-your-own-inference.md +126 -0
  53. package/docs/guides/tools-and-web-search.md +95 -0
  54. package/docs/index.md +14 -0
  55. package/docs/longhaul-35b-workorders.md +496 -0
  56. package/docs/memory-integration-analysis.md +303 -0
  57. package/docs/model-capability-awareness-and-multimodal-memory-root-fix.md +799 -0
  58. package/docs/multimodal-identity-memory-implementation.md +76 -0
  59. package/docs/omnius-self-edit-eval-2026-06-10.md +169 -0
  60. package/docs/opencode-agentic-loop-comparison.md +290 -0
  61. package/docs/operations/security-and-remote-access.md +2 -2
  62. package/docs/operations/version-compatibility.md +63 -0
  63. package/docs/proposals/git-progress-tracking-strategy.md +289 -0
  64. package/docs/proposals/opencode-modules/backendAdapter.ts +443 -0
  65. package/docs/proposals/opencode-modules/childSession.ts +288 -0
  66. package/docs/proposals/opencode-modules/compactionAgent.ts +101 -0
  67. package/docs/proposals/opencode-modules/orchestrator.ts +387 -0
  68. package/docs/proposals/opencode-modules/runner.ts +258 -0
  69. package/docs/reference/auth-map.md +87 -196
  70. package/docs/reference/configuration.md +27 -0
  71. package/docs/reference/rest-api.md +7 -0
  72. package/docs/reference/slash-commands.md +125 -2
  73. package/docs/research/_archived/README.md +18 -0
  74. package/docs/research/_archived/context_window_attention_model.py +418 -0
  75. package/docs/research/_archived/context_window_attention_spec.md +55 -0
  76. package/docs/research/_archived/context_window_attention_weights.json +68 -0
  77. package/docs/research/k-splanifolds.pdf +0 -0
  78. package/docs/research/personality-verbosity-control.md +293 -0
  79. package/docs/rest/INDEX.md +7 -0
  80. package/docs/rest/QUICKREF.md +18 -0
  81. package/docs/rest/REST-DOCS-MANIFEST.json +1 -0
  82. package/docs/rest/auth-and-scopes.md +7 -1
  83. package/docs/rest/endpoints/discovery.md +44 -0
  84. package/docs/rest/endpoints/events.md +5 -0
  85. package/docs/rest/endpoints/tools.md +9 -0
  86. package/docs/reviews/adversary-system-review.md +42 -0
  87. package/docs/sana-and-video-generation-integration-plan.md +712 -0
  88. package/docs/session-diary-llm-training-analysis.md +218 -0
  89. package/docs/telegram-dmn-curiosity-outreach-scaffold.md +91 -0
  90. package/docs/telegram-mid-horizon-download-loop-handoff.md +468 -0
  91. package/docs/telegram-reflection-corpus-integration-plan.md +306 -0
  92. package/docs/telegram-unified-tooling-architecture.md +332 -0
  93. package/docs/threat-model.md +868 -0
  94. package/docs/trajectory-grounding.md +160 -0
  95. package/docs/voice-flow-architecture.md +489 -0
  96. package/docs/work-orders/WO-AM-GAPS.md +638 -0
  97. package/docs/work-orders/daemon-hud-ui-overhaul.md +82 -0
  98. package/docs/work-orders/hermes-architecture-deltas/01-public-scrutiny-provenance-control/INDEX.md +21 -0
  99. package/docs/work-orders/hermes-architecture-deltas/01-public-scrutiny-provenance-control/WORKORDER.md +225 -0
  100. package/docs/work-orders/hermes-architecture-deltas/02-context-engine-plugin-boundary/INDEX.md +20 -0
  101. package/docs/work-orders/hermes-architecture-deltas/02-context-engine-plugin-boundary/WORKORDER.md +198 -0
  102. package/docs/work-orders/hermes-architecture-deltas/03-typed-gateway-event-stream/INDEX.md +19 -0
  103. package/docs/work-orders/hermes-architecture-deltas/03-typed-gateway-event-stream/WORKORDER.md +172 -0
  104. package/docs/work-orders/hermes-architecture-deltas/04-task-local-gateway-context/INDEX.md +19 -0
  105. package/docs/work-orders/hermes-architecture-deltas/04-task-local-gateway-context/WORKORDER.md +169 -0
  106. package/docs/work-orders/hermes-architecture-deltas/05-process-lifecycle-monitoring-notifications/INDEX.md +22 -0
  107. package/docs/work-orders/hermes-architecture-deltas/05-process-lifecycle-monitoring-notifications/WORKORDER.md +189 -0
  108. package/docs/work-orders/hermes-architecture-deltas/06-vision-evidence-routing-ladder/INDEX.md +22 -0
  109. package/docs/work-orders/hermes-architecture-deltas/06-vision-evidence-routing-ladder/WORKORDER.md +199 -0
  110. package/docs/work-orders/hermes-architecture-deltas/07-durable-multi-agent-kanban/INDEX.md +20 -0
  111. package/docs/work-orders/hermes-architecture-deltas/07-durable-multi-agent-kanban/WORKORDER.md +174 -0
  112. package/docs/work-orders/hermes-architecture-deltas/08-completion-critic-reconciliation-ledger/INDEX.md +22 -0
  113. package/docs/work-orders/hermes-architecture-deltas/08-completion-critic-reconciliation-ledger/WORKORDER.md +226 -0
  114. package/docs/work-orders/hermes-architecture-deltas/INDEX.md +38 -0
  115. package/docs/work-orders/omnius-context-engineering-behavior-fixes.md +281 -0
  116. package/docs/work-orders/telegram-dropbear-context-rca-workorder.md +202 -0
  117. package/docs/work-orders/world-class-memory-compiler/README.md +162 -0
  118. package/docs/work-orders/world-class-memory-compiler/TRACKER.md +179 -0
  119. package/docs/work-orders/world-class-memory-compiler/WO-01-exact-request-budget.md +79 -0
  120. package/docs/work-orders/world-class-memory-compiler/WO-02-typed-memory-fabric.md +65 -0
  121. package/docs/work-orders/world-class-memory-compiler/WO-03-dependency-working-set.md +55 -0
  122. package/docs/work-orders/world-class-memory-compiler/WO-04-inference-memory-compiler.md +67 -0
  123. package/docs/work-orders/world-class-memory-compiler/WO-05-artifact-fidelity-materialization.md +72 -0
  124. package/docs/work-orders/world-class-memory-compiler/WO-06-temporal-hybrid-retrieval.md +49 -0
  125. package/docs/work-orders/world-class-memory-compiler/WO-07-evaluation-harness.md +45 -0
  126. package/docs/work-orders/world-class-memory-compiler/WO-08-rollout-legacy-removal.md +45 -0
  127. package/docs/x402-remote-inference-plan.md +323 -0
  128. package/npm-shrinkwrap.json +108 -117
  129. package/package.json +7 -6
  130. package/templates/AGENTS.md +6 -0
  131. package/templates/OMNIUS.md +20 -0
@@ -0,0 +1,638 @@
1
+ # Associative Memory Gap Work Orders
2
+
3
+ Generated: 2026-04-13
4
+ Source: Deep audit of multimodal associative memory systems
5
+ Status: READY FOR IMPLEMENTATION
6
+
7
+ ---
8
+
9
+ ## WO-AM-GAP-01: Modality-Aware Episode Creation in Orchestrator
10
+
11
+ **Priority**: P0 (Critical — all downstream memory systems depend on correct modality tagging)
12
+ **Effort**: Small (15 lines changed)
13
+ **Risk**: Low (additive, no breaking changes)
14
+
15
+ ### Problem
16
+
17
+ The orchestrator hardcodes `modality: "tool_result"` for ALL tool results at `agenticRunner.ts:2900`. Visual, audio, social, and spatial tool results are stored with the wrong modality, making them invisible to modality-filtered queries and causing incorrect importance/decay auto-assignment.
18
+
19
+ ### Root Cause
20
+
21
+ Line 2900 in `packages/orchestrator/src/agenticRunner.ts`:
22
+ ```typescript
23
+ modality: "tool_result",
24
+ ```
25
+
26
+ This was written before the multimodal tools existed. Every tool — whether `file_read` or `vision` or `audio_capture` — gets the same modality tag.
27
+
28
+ ### Changes Required
29
+
30
+ **File 1: `packages/orchestrator/src/agenticRunner.ts`**
31
+
32
+ Location: Lines 2892-2902 (inside the post-tool-call episode insertion block)
33
+
34
+ Replace:
35
+ ```typescript
36
+ modality: "tool_result",
37
+ ```
38
+
39
+ With:
40
+ ```typescript
41
+ modality: inferEpisodeModality(tc.name),
42
+ ```
43
+
44
+ Add helper function (place near line 2890, before the insertion block):
45
+ ```typescript
46
+ function inferEpisodeModality(toolName: string): EpisodeModality {
47
+ // Visual tools
48
+ if (["vision", "camera_capture", "image_read", "screenshot", "ocr",
49
+ "ocr_image_advanced", "visual_memory", "desktop_describe"].includes(toolName)) {
50
+ return "visual";
51
+ }
52
+ // Audio tools
53
+ if (["audio_capture", "audio_analyze", "asr_listen", "transcribe_file",
54
+ "transcribe_url", "audio_playback"].includes(toolName)) {
55
+ return "audio";
56
+ }
57
+ // Social tools
58
+ if (["multimodal_memory", "send_message", "jibberlink"].includes(toolName)) {
59
+ return "social";
60
+ }
61
+ // Spatial tools
62
+ if (["gps_location", "bluetooth_scan", "wifi_control", "sdr_scan"].includes(toolName)) {
63
+ return "spatial";
64
+ }
65
+ // Code tools
66
+ if (["file_write", "file_edit", "file_patch", "batch_edit",
67
+ "code_sandbox", "repl_exec"].includes(toolName)) {
68
+ return "code";
69
+ }
70
+ return "tool_result";
71
+ }
72
+ ```
73
+
74
+ **File 2: `packages/orchestrator/src/agenticRunner.ts`** (imports)
75
+
76
+ Add import at the top (near existing memory imports):
77
+ ```typescript
78
+ import type { EpisodeModality } from "@omnius/memory";
79
+ ```
80
+
81
+ ### Upstream Dependencies
82
+ - None. The `EpisodeModality` type already includes all needed values.
83
+
84
+ ### Downstream Effects
85
+ - `autoImportance()` in `episodeStore.ts:115-152` will now correctly assign importance=7 for visual/audio episodes (currently gets 5 because modality is "tool_result")
86
+ - `autoDecayClass()` in `episodeStore.ts:154-179` will correctly assign "daily" for visual/audio (currently gets "daily" anyway for "tool_result", but this makes it explicit)
87
+ - The memory visualizer's episode view will now show correct modality icons
88
+ - PPR retrieval will be able to filter by modality in future queries
89
+
90
+ ### Success Metrics
91
+ - `pnpm test` in orchestrator package: all 387 tests pass
92
+ - `npx tsc --noEmit`: zero errors
93
+ - Manual test: call `vision(action="caption", path="test.png")` → verify episode has `modality: "visual"` in the database:
94
+ ```sql
95
+ SELECT modality FROM episodes ORDER BY timestamp DESC LIMIT 1;
96
+ -- Expected: "visual" (not "tool_result")
97
+ ```
98
+
99
+ ### Failure Modes to Avoid
100
+ - Do NOT change the modality for `task_complete` — it must remain handled separately at line 4256 as `modality: "gist"`
101
+ - Do NOT use the tool's category metadata — some tools are miscategorized. Use the explicit name list.
102
+ - The `inferEpisodeModality` function must be O(1) — use a Set lookup if the list grows beyond 30 tools.
103
+
104
+ ---
105
+
106
+ ## WO-AM-GAP-02: Bridge Multimodal JSON Store to SQLite EpisodeStore
107
+
108
+ **Priority**: P0 (Critical — multimodal episodes invisible to associative retrieval)
109
+ **Effort**: Medium (60 lines added)
110
+ **Risk**: Medium (touches two storage systems, must maintain consistency)
111
+
112
+ ### Problem
113
+
114
+ `multimodal-memory.ts` saves rich cross-modal episodes (CLIP embeddings, face IDs, transcripts, GPS) to JSON files at `~/.omnius/multimodal-episodes/`. This data is completely disconnected from the SQLite `episodeStore` used by PPR retrieval, zettelkasten linking, and orchestrator context injection.
115
+
116
+ Result: "Who did I meet yesterday?" only works via the multimodal_memory tool's `recall` action. The agent's automatic context injection (WO-AM-06 every 3 turns) never surfaces multimodal episodes.
117
+
118
+ ### Root Cause
119
+
120
+ `multimodal-memory.ts` was built as a standalone tool before the episode store existed. Its `saveEpisode()` method (line ~293) writes only to JSON, not to SQLite.
121
+
122
+ ### Changes Required
123
+
124
+ **File 1: `packages/execution/src/tools/multimodal-memory.ts`**
125
+
126
+ Location: After `this.saveEpisode(episode)` call (line 293 in captureEpisode, line 381 in meetPerson)
127
+
128
+ Add episode store bridge call:
129
+ ```typescript
130
+ // Bridge to SQLite episode store for PPR retrieval + zettelkasten linking
131
+ this.bridgeToEpisodeStore(episode);
132
+ ```
133
+
134
+ Add the bridge method to the class:
135
+ ```typescript
136
+ private bridgeToEpisodeStore(episode: MultiModalEpisode): void {
137
+ try {
138
+ // Dynamic import to avoid hard dependency on memory package in execution
139
+ const { EpisodeStore, TemporalGraph } = require("@omnius/memory");
140
+ const { join } = require("node:path");
141
+
142
+ // Find the project's .omnius_test directory (or .omnius/)
143
+ const omniusDirs = [
144
+ join(process.cwd(), ".omnius_test"),
145
+ join(process.cwd(), ".omnius"),
146
+ ];
147
+ const omniusDir = omniusDirs.find(d => require("node:fs").existsSync(d));
148
+ if (!omniusDir) return;
149
+
150
+ const es = new EpisodeStore(join(omniusDir, "memory.db"));
151
+ const tg = new TemporalGraph(join(omniusDir, "kg.db"));
152
+
153
+ // Build composite content from all modalities
154
+ const contentParts: string[] = [];
155
+ if (episode.social?.personName) contentParts.push(`Met ${episode.social.personName}`);
156
+ if (episode.audio?.transcript) contentParts.push(`Said: "${episode.audio.transcript}"`);
157
+ if (episode.audio?.soundClass) contentParts.push(`Sound: ${episode.audio.soundClass}`);
158
+ if (episode.visual?.faceNames?.length) contentParts.push(`Faces: ${episode.visual.faceNames.join(", ")}`);
159
+ if (episode.visual?.objects?.length) contentParts.push(`Objects: ${episode.visual.objects.join(", ")}`);
160
+ if (episode.spatial?.locationLabel) contentParts.push(`Location: ${episode.spatial.locationLabel}`);
161
+ const content = contentParts.join(". ") || episode.text?.content || "Multimodal capture";
162
+
163
+ // Determine primary modality
164
+ const modality = episode.social?.personName ? "social"
165
+ : episode.visual?.imagePath ? "visual"
166
+ : episode.audio?.recordingPath ? "audio"
167
+ : "text";
168
+
169
+ // Insert episode with CLIP embedding if available
170
+ const epId = es.insert({
171
+ sessionId: episode.sessionId,
172
+ modality,
173
+ toolName: "multimodal_memory",
174
+ content,
175
+ importance: episode.social?.personName ? 9 : 7,
176
+ decayClass: episode.social?.personName ? "procedural" : "daily",
177
+ metadata: {
178
+ multimodal_episode_id: episode.id,
179
+ has_face: (episode.visual?.faceIds?.length ?? 0) > 0,
180
+ has_audio: !!episode.audio?.recordingPath,
181
+ has_gps: !!episode.spatial?.gps,
182
+ },
183
+ });
184
+
185
+ // Set CLIP embedding if available (512d → Float32Array)
186
+ if (episode.visual?.clipEmbedding) {
187
+ const emb = new Float32Array(episode.visual.clipEmbedding);
188
+ es.setEmbedding(epId, emb);
189
+ }
190
+
191
+ // Create KG nodes for entities
192
+ if (episode.social?.personName) {
193
+ const personId = tg.upsertNode({ text: episode.social.personName, nodeType: "person" });
194
+ tg.addEdge({
195
+ srcId: personId, dstId: personId,
196
+ relation: "discovered_during",
197
+ fact: `Met ${episode.social.personName} via multimodal capture`,
198
+ edgeType: "triple",
199
+ sourceEpisodeId: epId,
200
+ modality,
201
+ });
202
+ }
203
+
204
+ if (episode.spatial?.locationLabel) {
205
+ const locId = tg.upsertNode({ text: episode.spatial.locationLabel, nodeType: "location" });
206
+ if (episode.social?.personName) {
207
+ const personId = tg.findNode(episode.social.personName, "person")?.id;
208
+ if (personId) {
209
+ tg.addEdge({
210
+ srcId: personId, dstId: locId,
211
+ relation: "appears_in",
212
+ fact: `${episode.social.personName} seen at ${episode.spatial.locationLabel}`,
213
+ sourceEpisodeId: epId,
214
+ });
215
+ }
216
+ }
217
+ }
218
+
219
+ es.close();
220
+ tg.close();
221
+ } catch {
222
+ // Non-critical — multimodal memory works without SQLite bridge
223
+ }
224
+ }
225
+ ```
226
+
227
+ ### Upstream Dependencies
228
+ - `@omnius/memory` must be available at runtime (it is — it's a workspace dependency)
229
+ - The `.omnius_test/` or `.omnius/` directory must exist (created by the daemon on startup)
230
+
231
+ ### Downstream Effects
232
+ - PPR retrieval (`pprRetrieval.ts`) will now find multimodal episodes via person/location nodes
233
+ - Zettelkasten linking will create edges from multimodal episodes to text episodes
234
+ - The memory visualizer will show multimodal episodes with correct modalities
235
+ - Orchestrator context injection (every 3 turns) will surface "Met Alice" episodes
236
+
237
+ ### Success Metrics
238
+ - Call `multimodal_memory(action="capture")` → verify episode appears in both:
239
+ - `~/.omnius/multimodal-episodes/{id}/episode.json` (JSON)
240
+ - `SELECT * FROM episodes WHERE tool_name='multimodal_memory' ORDER BY timestamp DESC LIMIT 1` (SQLite)
241
+ - Call `multimodal_memory(action="meet", person_name="Test")` → verify KG node:
242
+ - `SELECT * FROM kg_nodes WHERE text='Test' AND node_type='person'`
243
+
244
+ ### Failure Modes to Avoid
245
+ - Use `try/catch` around the entire bridge — multimodal_memory must work even if SQLite is unavailable
246
+ - Use `require()` not static import — execution package should not hard-depend on memory package at build time
247
+ - Do NOT duplicate the CLIP embedding computation — reuse the one already computed in captureEpisode
248
+ - Close DB connections immediately after use (the tool is not a long-lived service)
249
+
250
+ ---
251
+
252
+ ## WO-AM-GAP-03: Cross-Modal Embedding Space Alignment
253
+
254
+ **Priority**: P1 (Important — blocks zettelkasten linking across modalities)
255
+ **Effort**: Large (100+ lines, architectural decision required)
256
+ **Risk**: High (changes embedding semantics, may invalidate existing embeddings)
257
+
258
+ ### Problem
259
+
260
+ The zettelkasten linker (`zettelkasten.ts`) links episodes by cosine similarity of their embeddings. But episodes from different modalities have incompatible embeddings:
261
+
262
+ - Text episodes: nomic-embed-text 768d
263
+ - Visual episodes: OpenCLIP ViT-B/32 512d
264
+ - Audio episodes: ECAPA-TDNN 192d
265
+
266
+ The linker silently skips pairs with mismatched dimensions (episodeStore.ts:306 — `if (ep.embedding.length === qEmb.length)`). Cross-modal linking never happens.
267
+
268
+ ### Root Cause
269
+
270
+ The embedding pipeline was built per-modality without a unification strategy. Each modality uses a different model with different dimensions.
271
+
272
+ ### Solution Options (choose one)
273
+
274
+ **Option A: Unified CLIP Space (recommended)**
275
+
276
+ Use CLIP's text encoder for ALL episode embeddings. CLIP's text and image encoders share a 512d space, so text descriptions of visual content will be similar to actual visual embeddings.
277
+
278
+ Changes:
279
+ - `packages/memory/src/embeddings.ts`: Add `generateCLIPTextEmbedding()` function that calls OpenCLIP text encoder (512d) instead of nomic-embed-text (768d)
280
+ - `packages/cli/src/api/embedding-workers.ts`: Route text episodes through CLIP text encoder when they're related to multimodal content
281
+ - `scripts/embed-text.py`: Add CLIP text embedding mode
282
+
283
+ Pros: All embeddings in same space, cross-modal cosine works naturally
284
+ Cons: Requires re-embedding existing text episodes (migration), CLIP text embeddings are weaker for pure-text similarity than nomic-embed
285
+
286
+ **Option B: Dual Embedding (store both)**
287
+
288
+ Store two embeddings per episode: one in the modality's native space, one in a shared CLIP text space.
289
+
290
+ Changes:
291
+ - `packages/memory/src/episodeStore.ts`: Add `clip_embedding BLOB` column
292
+ - `packages/memory/src/zettelkasten.ts`: Use `clip_embedding` for cross-modal linking, `embedding` for within-modality
293
+
294
+ Pros: Best of both worlds, no migration needed for existing data
295
+ Cons: Doubles embedding storage, more complex query logic
296
+
297
+ **Option C: Projection Layer**
298
+
299
+ Learn a linear projection from each modality's space to a shared 512d space.
300
+
301
+ Cons: Requires training data, complex. Not recommended for now.
302
+
303
+ ### Implementation (Option A — Unified CLIP Space)
304
+
305
+ **File 1: `scripts/embed-text.py`**
306
+
307
+ Add CLIP text mode:
308
+ ```python
309
+ def embed_clip_text(text):
310
+ """Embed text using CLIP text encoder for cross-modal matching."""
311
+ import open_clip
312
+ model, _, _ = open_clip.create_model_and_transforms('ViT-B-32', pretrained='laion2b_s34b_b79k')
313
+ tokenizer = open_clip.get_tokenizer('ViT-B-32')
314
+ tokens = tokenizer([text])
315
+ with torch.no_grad():
316
+ emb = model.encode_text(tokens)
317
+ emb = emb / emb.norm(dim=-1, keepdim=True)
318
+ return emb.squeeze(0).cpu().numpy().tolist()
319
+ ```
320
+
321
+ **File 2: `packages/cli/src/api/py-embed.ts`**
322
+
323
+ Add function:
324
+ ```typescript
325
+ export function runEmbedTextCLIP(input: { text: string }): Float32Array | null {
326
+ // Calls embed-text.py with --mode clip
327
+ }
328
+ ```
329
+
330
+ **File 3: `packages/cli/src/api/embedding-workers.ts`**
331
+
332
+ In the visual embedding worker (line ~105), after computing the CLIP image embedding, also compute CLIP text embedding for the episode content and store it. This ensures text content associated with visual episodes lives in the same embedding space.
333
+
334
+ **File 4: `packages/memory/src/zettelkasten.ts`**
335
+
336
+ In `findNeighbors()` (line ~60), add dimension check with fallback:
337
+ ```typescript
338
+ // If dimensions mismatch, skip (different embedding spaces)
339
+ if (a.length !== b.length) continue;
340
+ ```
341
+ This already exists. With unified CLIP space, all cross-modal episodes will have matching 512d embeddings.
342
+
343
+ ### Success Metrics
344
+ - Visual episode (512d CLIP image) and text episode (512d CLIP text) can be linked by zettelkasten
345
+ - `cosineSimilarity(clipImageEmb, clipTextEmb)` > 0.2 for related content
346
+ - Existing text-only episodes continue to work with nomic-embed (they keep their 768d embeddings and link to each other)
347
+
348
+ ### Failure Modes to Avoid
349
+ - Do NOT replace nomic-embed for pure-text episodes — CLIP text is weaker for text-text similarity
350
+ - Do NOT force re-embedding on startup — make it incremental (new episodes get CLIP, old ones keep nomic)
351
+ - Handle the case where OpenCLIP is not installed gracefully (vision-ml-venv may not exist)
352
+
353
+ ---
354
+
355
+ ## WO-AM-GAP-04: Queryable Audio Features (FFT, Band Energy, Sound Class)
356
+
357
+ **Priority**: P1 (Important — rich audio data captured but not searchable)
358
+ **Effort**: Medium (40 lines)
359
+ **Risk**: Low (additive, extends existing search)
360
+
361
+ ### Problem
362
+
363
+ `audio-analyze.ts` computes FFT peak frequencies, 5-band energy distribution, RMS levels, and YAMNet sound classifications. These are stored in episode `metadata` as JSON but the episode store's `search()` function at `episodeStore.ts:260` only searches `content` text and `embedding` vectors. Metadata fields are opaque.
364
+
365
+ ### Root Cause
366
+
367
+ The episode search was designed for text retrieval + embedding similarity. Structured metadata filtering was planned (WO-AM-03) but never implemented.
368
+
369
+ ### Changes Required
370
+
371
+ **File 1: `packages/memory/src/episodeStore.ts`**
372
+
373
+ Location: After the `EpisodeQuery` interface (line 62-72)
374
+
375
+ Add metadata filter fields:
376
+ ```typescript
377
+ export interface EpisodeQuery {
378
+ // ... existing fields ...
379
+ /** Filter by metadata key-value (exact match) */
380
+ metadataFilter?: Record<string, unknown>;
381
+ /** Filter by sound class (YAMNet classification) */
382
+ soundClass?: string;
383
+ /** Filter by RMS level range */
384
+ rmsRange?: { min?: number; max?: number };
385
+ }
386
+ ```
387
+
388
+ Location: In the `search()` method (line ~267-279), after the SQL WHERE clause builder
389
+
390
+ Add metadata filtering in the post-fetch scoring phase:
391
+ ```typescript
392
+ // Metadata filtering (post-fetch, JSON parsing)
393
+ if (query.metadataFilter || query.soundClass || query.rmsRange) {
394
+ candidates = candidates.filter(ep => {
395
+ if (!ep.metadata) return false;
396
+ const meta = typeof ep.metadata === "string" ? JSON.parse(ep.metadata) : ep.metadata;
397
+
398
+ if (query.metadataFilter) {
399
+ for (const [k, v] of Object.entries(query.metadataFilter)) {
400
+ if (meta[k] !== v) return false;
401
+ }
402
+ }
403
+ if (query.soundClass && meta.sound_class !== query.soundClass) return false;
404
+ if (query.rmsRange) {
405
+ const rms = meta.rms_db ?? meta.rmsDb;
406
+ if (typeof rms !== "number") return false;
407
+ if (query.rmsRange.min !== undefined && rms < query.rmsRange.min) return false;
408
+ if (query.rmsRange.max !== undefined && rms > query.rmsRange.max) return false;
409
+ }
410
+ return true;
411
+ });
412
+ }
413
+ ```
414
+
415
+ **File 2: `packages/execution/src/tools/audio-analyze.ts`**
416
+
417
+ Location: After the tool result is returned from each analysis action
418
+
419
+ Ensure metadata is stored on the episode by including it in the tool result content:
420
+ ```typescript
421
+ // In classifyAudio (line ~130):
422
+ // Already returns JSON with classifications — content will be captured by orchestrator
423
+
424
+ // In analyzeSpectrum (line ~230):
425
+ // Already returns JSON with peak_frequencies, band_energy_db, rms_db
426
+ ```
427
+
428
+ The orchestrator already stores the tool output as episode content (line 2894-2895). The metadata is embedded in the content string. For structured query, we need to also store key features in the `metadata` field.
429
+
430
+ **File 3: `packages/orchestrator/src/agenticRunner.ts`**
431
+
432
+ Location: Lines 2892-2910 (episode insertion block)
433
+
434
+ After `const episodeContent = ...`, add metadata extraction for audio tools:
435
+ ```typescript
436
+ let episodeMetadata: Record<string, unknown> = {
437
+ args_fingerprint: argsKey.slice(0, 200),
438
+ success: result.success,
439
+ duration_ms: performance.now() - toolStart,
440
+ };
441
+
442
+ // Extract structured audio features into metadata for queryable filtering
443
+ if (result.success && ["audio_analyze", "audio_capture"].includes(tc.name)) {
444
+ try {
445
+ const parsed = JSON.parse(result.output ?? "{}");
446
+ if (parsed.rms_db !== undefined) episodeMetadata.rms_db = parsed.rms_db;
447
+ if (parsed.classifications) episodeMetadata.sound_class = parsed.classifications[0]?.class;
448
+ if (parsed.peak_frequencies) episodeMetadata.peak_frequencies = parsed.peak_frequencies.slice(0, 3);
449
+ if (parsed.band_energy_db) episodeMetadata.band_energy = parsed.band_energy_db;
450
+ } catch { /* tool output may not be JSON */ }
451
+ }
452
+ ```
453
+
454
+ ### Success Metrics
455
+ - `episodeStore.search({ soundClass: "Speech" })` returns only audio episodes classified as speech
456
+ - `episodeStore.search({ rmsRange: { min: -30 } })` returns only loud audio episodes
457
+ - Existing text search continues to work unchanged
458
+
459
+ ### Failure Modes to Avoid
460
+ - Metadata filtering is post-fetch (JavaScript), not SQL WHERE — this is intentional. SQLite JSON operators are slow on large datasets. For <10K episodes this is fine.
461
+ - Do NOT parse every episode's metadata on search — only parse if `metadataFilter`, `soundClass`, or `rmsRange` are specified in the query
462
+ - Handle non-JSON metadata gracefully (some old episodes may have string metadata)
463
+
464
+ ---
465
+
466
+ ## WO-AM-GAP-05: Activate Ebbinghaus Strength in Scoring Formula
467
+
468
+ **Priority**: P2 (Enhancement — strength is computed but unused)
469
+ **Effort**: Small (5 lines changed)
470
+ **Risk**: Low (additive scoring factor)
471
+
472
+ ### Problem
473
+
474
+ The Ebbinghaus strength field is incremented on every retrieval (`episodeStore.ts:325-330`) but never used in the scoring formula at line 318. The score is `recency + importance + relevance` — strength is a dead field that grows but has no effect on ranking.
475
+
476
+ ### Root Cause
477
+
478
+ Planned for WO-AM-02 (per code comments) but never implemented.
479
+
480
+ ### Changes Required
481
+
482
+ **File 1: `packages/memory/src/episodeStore.ts`**
483
+
484
+ Location: Line 318 (the scoring formula)
485
+
486
+ Replace:
487
+ ```typescript
488
+ const score = recency + importance + relevance;
489
+ ```
490
+
491
+ With:
492
+ ```typescript
493
+ // Ebbinghaus strength bonus: frequently retrieved episodes surface higher.
494
+ // Log scale prevents runaway scores from heavily-retrieved episodes.
495
+ // strength=1 (never retrieved) → bonus=0
496
+ // strength=3 (retrieved twice) → bonus=0.48
497
+ // strength=10 (retrieved 9 times) → bonus=1.0
498
+ const strengthBonus = Math.min(1.0, Math.log2(Math.max(1, ep.strength)));
499
+ const score = recency + importance + relevance + strengthBonus;
500
+ ```
501
+
502
+ ### Upstream Dependencies
503
+ - None. `ep.strength` is already populated by `insert()` (default 1.0) and incremented by `search()` (line 325).
504
+
505
+ ### Downstream Effects
506
+ - Episodes that are frequently retrieved will rank higher in future searches
507
+ - The maximum score increases from 4.0 to 5.0 (recency 1.0 + importance 1.0 + relevance 2.0 + strength 1.0)
508
+ - PPR retrieval uses its own scoring (PPR scores from PageRank), so this only affects direct episode search
509
+
510
+ ### Success Metrics
511
+ - Insert episode A and B with same content/importance/timestamp
512
+ - Search and retrieve A three times (strength goes to 4)
513
+ - Search again — A should rank higher than B due to strength bonus
514
+ - `strengthBonus(4) = log2(4) = 2.0` → capped to 1.0
515
+
516
+ ### Failure Modes to Avoid
517
+ - Use `log2` not linear — linear strength would make old frequently-retrieved episodes dominate forever
518
+ - Cap at 1.0 — strength should be a tiebreaker, not the dominant factor
519
+ - Do NOT modify the PPR scoring in `pprRetrieval.ts` — that uses graph-based scores, not episode scores
520
+ - Ensure `Math.max(1, ep.strength)` to handle any episodes where strength is 0 or null
521
+
522
+ ---
523
+
524
+ ## WO-AM-GAP-06: Modality-Aware Decay Classification
525
+
526
+ **Priority**: P2 (Enhancement — more realistic memory dynamics)
527
+ **Effort**: Small (15 lines changed)
528
+ **Risk**: Low (changes defaults, existing episodes keep their assigned decay)
529
+
530
+ ### Problem
531
+
532
+ `autoDecayClass()` in `episodeStore.ts:154-179` assigns `"daily"` (24h half-life) to visual and audio episodes. But:
533
+ - Face memories should last weeks/months (humans remember faces for years)
534
+ - Social associations (names) should be nearly permanent
535
+ - Speech content decays faster than faces
536
+ - Tool results (`file_read` output) decay fastest
537
+
538
+ ### Root Cause
539
+
540
+ The decay classification was written with a focus on text tool results. When multimodal modalities were added, they all got `"daily"` as a reasonable default without deeper consideration.
541
+
542
+ ### Changes Required
543
+
544
+ **File 1: `packages/memory/src/episodeStore.ts`**
545
+
546
+ Location: Lines 154-179 (`autoDecayClass()` function)
547
+
548
+ Replace the multimodal section:
549
+ ```typescript
550
+ // Current (lines ~168-170):
551
+ if (["visual", "audio", "social", "spatial"].includes(modality)) return "daily";
552
+ ```
553
+
554
+ With modality-specific decay:
555
+ ```typescript
556
+ // Modality-specific decay — matches human memory dynamics
557
+ if (modality === "social") return "permanent"; // Names, relationships persist
558
+ if (modality === "visual") return "procedural"; // Faces last weeks/months
559
+ if (modality === "spatial") return "procedural"; // Locations persist
560
+ if (modality === "audio") return "daily"; // Speech content fades in days
561
+ ```
562
+
563
+ Also update `autoImportance()` at lines 134-135 to differentiate:
564
+ ```typescript
565
+ // Current:
566
+ if (modality === "visual" || modality === "audio") return 7;
567
+ if (modality === "social") return 8;
568
+
569
+ // Updated:
570
+ if (modality === "social") return 9; // People are most important
571
+ if (modality === "visual") return 7; // Sights are notable
572
+ if (modality === "audio") return 6; // Sounds are contextual
573
+ if (modality === "spatial") return 5; // Locations are background
574
+ ```
575
+
576
+ ### Downstream Effects
577
+ - Social episodes ("Met Alice") will never decay — they'll always be retrievable
578
+ - Visual episodes (face detections) will persist for 30 days before 50% decay
579
+ - Audio episodes (speech, sounds) will decay in 24h (same as before)
580
+ - The memory visualizer's episode view will show more variety in decay indicators
581
+
582
+ ### Success Metrics
583
+ - `autoDecayClass("social")` returns `"permanent"`
584
+ - `autoDecayClass("visual")` returns `"procedural"`
585
+ - `autoDecayClass("audio")` returns `"daily"` (unchanged)
586
+ - Social episode from 7 days ago still has recency > 0.95 (permanent, tau=infinity)
587
+ - Visual episode from 7 days ago has recency ~0.85 (procedural, tau=30d)
588
+
589
+ ### Failure Modes to Avoid
590
+ - Do NOT retroactively change existing episodes' decay class — only affect new inserts
591
+ - Do NOT make tool_result episodes permanent — they must decay (session or daily)
592
+ - Test that `pruneExpired()` still works — it only prunes `session` class episodes older than 3h
593
+
594
+ ---
595
+
596
+ ## Implementation Order
597
+
598
+ | Order | Work Order | Depends On | Estimated Time |
599
+ |-------|-----------|-----------|----------------|
600
+ | 1 | WO-AM-GAP-01 (Modality tagging) | None | 30 min |
601
+ | 2 | WO-AM-GAP-06 (Modality decay) | GAP-01 | 15 min |
602
+ | 3 | WO-AM-GAP-05 (Ebbinghaus activation) | None | 15 min |
603
+ | 4 | WO-AM-GAP-02 (Multimodal bridge) | GAP-01 | 1 hour |
604
+ | 5 | WO-AM-GAP-04 (Audio features query) | GAP-01 | 45 min |
605
+ | 6 | WO-AM-GAP-03 (Embedding alignment) | GAP-02 | 2-3 hours |
606
+
607
+ GAP-01 is the foundation — all others build on correct modality tagging. GAP-03 is the largest and should be done last after validating the simpler fixes.
608
+
609
+ ---
610
+
611
+ ## Verification Checklist
612
+
613
+ After all work orders are implemented:
614
+
615
+ - [ ] `npx tsc --noEmit -p packages/orchestrator/tsconfig.json` — zero errors
616
+ - [ ] `npx tsc --noEmit -p packages/memory/tsconfig.json` — zero errors (new types)
617
+ - [ ] `pnpm test` in packages/orchestrator — all tests pass
618
+ - [ ] `pnpm test` in packages/memory — all tests pass
619
+ - [ ] Manual: `vision(action="caption")` → episode has `modality: "visual"` in SQLite
620
+ - [ ] Manual: `audio_capture(duration=3)` → episode has `modality: "audio"` in SQLite
621
+ - [ ] Manual: `multimodal_memory(action="meet", person_name="Test")` → episode in BOTH JSON and SQLite
622
+ - [ ] Manual: `/memory episodes` → shows visual/audio/social episodes with correct icons
623
+ - [ ] Manual: Episode search returns cross-modal results after GAP-03
624
+ - [ ] Manual: Frequently retrieved episode ranks higher after GAP-05
625
+
626
+ ---
627
+
628
+ ## Research References
629
+
630
+ - Generative Agents (2304.03442) — triple-factor retrieval
631
+ - HippoRAG (2405.14831) — PPR over knowledge graphs
632
+ - JARVIS-1 (2311.05997) — two-stage cross-modal retrieval
633
+ - A-MEM (2502.12110) — retroactive memory evolution
634
+ - MemoryOS (2506.06326) — 3-tier memory with heat-score eviction
635
+ - ReadAgent (2402.09727) — gist compression (3.5-20x context extension)
636
+ - Graphiti (getzep/graphiti) — temporal knowledge graph with fact supersession
637
+ - MemoryBank — Ebbinghaus strength increments on retrieval
638
+ - RGMem (2510.16392) — phase-transition threshold for abstraction