ltcai 11.5.2 → 11.6.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 (439) hide show
  1. package/README.md +91 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +75 -0
  4. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  5. package/docs/DEVELOPMENT.md +8 -6
  6. package/docs/ENTERPRISE.md +2 -1
  7. package/docs/MULTI_AGENT_RUNTIME.md +12 -5
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -3
  10. package/docs/REALTIME_COLLABORATION.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/WORKFLOW_DESIGNER.md +3 -2
  14. package/docs/kg-schema.md +1 -1
  15. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  16. package/lattice_brain/__init__.py +42 -79
  17. package/lattice_brain/graph/__init__.py +9 -24
  18. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  19. package/lattice_brain/ingestion/__init__.py +19 -58
  20. package/lattice_brain/ingestion/pipeline.py +20 -398
  21. package/lattice_brain/multimodal/__init__.py +7 -18
  22. package/lattice_brain/multimodal/images.py +6 -264
  23. package/lattice_brain/multimodal/video.py +8 -247
  24. package/lattice_brain/runtime/__init__.py +8 -79
  25. package/lattice_brain/runtime/hooks.py +22 -584
  26. package/latticeai/__init__.py +1 -1
  27. package/latticeai/api/agent_worker_seam.py +7 -80
  28. package/latticeai/api/health.py +6 -20
  29. package/latticeai/api/local_files.py +16 -613
  30. package/latticeai/api/models.py +12 -87
  31. package/latticeai/api/search.py +17 -255
  32. package/latticeai/api/tools.py +69 -750
  33. package/latticeai/api/voice_capture.py +8 -70
  34. package/latticeai/api/worker_compute.py +842 -0
  35. package/latticeai/api/worker_seams.py +216 -0
  36. package/latticeai/app_factory.py +23 -232
  37. package/latticeai/cli/entrypoint.py +36 -249
  38. package/latticeai/core/agent_permission.py +16 -91
  39. package/latticeai/core/messages.py +89 -531
  40. package/latticeai/runtime/access_runtime.py +0 -14
  41. package/latticeai/runtime/bootstrap.py +10 -21
  42. package/latticeai/runtime/brain_runtime.py +35 -43
  43. package/latticeai/runtime/build_phases/__init__.py +21 -37
  44. package/latticeai/runtime/build_phases/features.py +99 -389
  45. package/latticeai/runtime/build_phases/foundation.py +82 -392
  46. package/latticeai/runtime/build_phases/web.py +91 -408
  47. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  48. package/latticeai/runtime/lifespan_runtime.py +7 -16
  49. package/latticeai/runtime/platform_services_runtime.py +1 -15
  50. package/latticeai/runtime/runtime_context.py +13 -130
  51. package/latticeai/runtime/security_runtime.py +10 -103
  52. package/latticeai/services/architecture_readiness.py +82 -43
  53. package/latticeai/services/model_runtime/__init__.py +2 -11
  54. package/latticeai/services/model_runtime/service.py +4 -28
  55. package/latticeai/services/p_reinforce.py +10 -261
  56. package/latticeai/services/product_readiness.py +31 -38
  57. package/latticeai/services/search_service.py +37 -795
  58. package/latticeai/services/tool_dispatch.py +30 -386
  59. package/latticeai/services/voice_capture.py +13 -107
  60. package/latticeai/tools/__init__.py +22 -49
  61. package/latticeai/tools/commands.py +0 -163
  62. package/latticeai/tools/computer.py +0 -39
  63. package/latticeai/tools/documents.py +1 -134
  64. package/latticeai/tools/filesystem.py +1 -247
  65. package/latticeai/tools/knowledge.py +1 -52
  66. package/latticeai/tools/local_files.py +0 -20
  67. package/latticeai/worker_app.py +75 -0
  68. package/package.json +2 -3
  69. package/requirements.txt +0 -5
  70. package/scripts/agent_eval.py +16 -28
  71. package/scripts/brain_quality_eval.py +20 -183
  72. package/scripts/bump_version.py +5 -5
  73. package/scripts/check_current_release_docs.mjs +7 -4
  74. package/scripts/check_openapi_drift.mjs +10 -0
  75. package/scripts/check_server_i18n.mjs +2 -18
  76. package/scripts/compose_openapi.py +377 -0
  77. package/scripts/export_openapi.py +32 -4
  78. package/scripts/gen_messages_catalog_fixture.py +302 -0
  79. package/scripts/gen_openapi_fragments.py +365 -0
  80. package/scripts/gen_redact_fixture.py +196 -0
  81. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  82. package/scripts/generate_agent_parity_fixtures.py +33 -14
  83. package/scripts/openapi_route_families.json +2161 -0
  84. package/scripts/release_screen_claims.json +21 -0
  85. package/scripts/run_integration_tests.mjs +134 -29
  86. package/scripts/run_sidecar_e2e.mjs +104 -11
  87. package/scripts/wheel_smoke.py +32 -22
  88. package/src-tauri/Cargo.lock +375 -9
  89. package/src-tauri/Cargo.toml +1 -1
  90. package/src-tauri/src/backend.rs +151 -45
  91. package/src-tauri/src/main.rs +18 -18
  92. package/src-tauri/src/topology.rs +58 -88
  93. package/src-tauri/tauri.conf.json +1 -2
  94. package/static/app/asset-manifest.json +41 -41
  95. package/static/app/assets/{Act-DcQizkl1.js → Act-CToZnOHz.js} +1 -1
  96. package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
  100. package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
  107. package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
  108. package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
  112. package/static/app/assets/circle-check-CY20XMCh.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
  119. package/static/app/assets/index-BovSWRmQ.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
  124. package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
  125. package/static/app/assets/search-DTxtjQ07.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.js} +1 -1
  134. package/static/app/index.html +4 -4
  135. package/static/sw.js +1 -1
  136. package/lattice_brain/archive.py +0 -522
  137. package/lattice_brain/context.py +0 -325
  138. package/lattice_brain/conversations.py +0 -382
  139. package/lattice_brain/core.py +0 -82
  140. package/lattice_brain/graph/_kg_contract.py +0 -249
  141. package/lattice_brain/graph/curator.py +0 -676
  142. package/lattice_brain/graph/discovery.py +0 -595
  143. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  144. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  145. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  146. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  147. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  148. package/lattice_brain/graph/documents.py +0 -380
  149. package/lattice_brain/graph/fusion.py +0 -395
  150. package/lattice_brain/graph/identity.py +0 -175
  151. package/lattice_brain/graph/image_vectors.py +0 -230
  152. package/lattice_brain/graph/ingest.py +0 -829
  153. package/lattice_brain/graph/network.py +0 -205
  154. package/lattice_brain/graph/proactive.py +0 -724
  155. package/lattice_brain/graph/projection/__init__.py +0 -42
  156. package/lattice_brain/graph/projection/curation.py +0 -500
  157. package/lattice_brain/graph/projection/v2_schema.py +0 -518
  158. package/lattice_brain/graph/provenance.py +0 -524
  159. package/lattice_brain/graph/rerank.py +0 -163
  160. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  161. package/lattice_brain/graph/retrieval/context.py +0 -197
  162. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  163. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  164. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  165. package/lattice_brain/graph/retrieval/signals.py +0 -95
  166. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  167. package/lattice_brain/graph/retrieval_policy.py +0 -180
  168. package/lattice_brain/graph/retrieval_reads.py +0 -769
  169. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  170. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  171. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  172. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  173. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  174. package/lattice_brain/graph/schema.py +0 -792
  175. package/lattice_brain/graph/store.py +0 -268
  176. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  177. package/lattice_brain/graph/vector_index/base.py +0 -167
  178. package/lattice_brain/graph/vector_index/brute_force.py +0 -110
  179. package/lattice_brain/graph/vector_index/hnsw.py +0 -290
  180. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  181. package/lattice_brain/graph/vector_index/quantized.py +0 -148
  182. package/lattice_brain/graph/vector_index/selector.py +0 -161
  183. package/lattice_brain/graph/write_master.py +0 -308
  184. package/lattice_brain/ingestion/_contract.py +0 -90
  185. package/lattice_brain/ingestion/folder_scan.py +0 -57
  186. package/lattice_brain/ingestion/folders.py +0 -258
  187. package/lattice_brain/ingestion/jobs_api.py +0 -107
  188. package/lattice_brain/ingestion/routing.py +0 -295
  189. package/lattice_brain/ingestion_jobs.py +0 -380
  190. package/lattice_brain/memory.py +0 -75
  191. package/lattice_brain/portability/__init__.py +0 -90
  192. package/lattice_brain/portability/_contract.py +0 -42
  193. package/lattice_brain/portability/backups.py +0 -338
  194. package/lattice_brain/portability/bundles.py +0 -136
  195. package/lattice_brain/portability/constants.py +0 -93
  196. package/lattice_brain/portability/fsops.py +0 -138
  197. package/lattice_brain/portability/service.py +0 -41
  198. package/lattice_brain/portability/sharing.py +0 -710
  199. package/lattice_brain/quality.py +0 -543
  200. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  201. package/lattice_brain/runtime/agent_runtime.py +0 -859
  202. package/lattice_brain/runtime/contracts.py +0 -460
  203. package/lattice_brain/runtime/multi_agent.py +0 -942
  204. package/lattice_brain/runtime/statuses.py +0 -10
  205. package/lattice_brain/sealed_box.py +0 -240
  206. package/lattice_brain/self_model.py +0 -675
  207. package/lattice_brain/sensitivity.py +0 -94
  208. package/lattice_brain/storage/__init__.py +0 -22
  209. package/lattice_brain/storage/base.py +0 -100
  210. package/lattice_brain/storage/docker.py +0 -105
  211. package/lattice_brain/storage/factory.py +0 -31
  212. package/lattice_brain/storage/migration.py +0 -191
  213. package/lattice_brain/storage/postgres.py +0 -123
  214. package/lattice_brain/storage/sqlite.py +0 -143
  215. package/lattice_brain/synthesis.py +0 -824
  216. package/lattice_brain/workflow.py +0 -497
  217. package/latticeai/api/admin.py +0 -471
  218. package/latticeai/api/agent_registry.py +0 -105
  219. package/latticeai/api/agents.py +0 -228
  220. package/latticeai/api/auth.py +0 -383
  221. package/latticeai/api/automation_intelligence.py +0 -401
  222. package/latticeai/api/brain_intelligence.py +0 -199
  223. package/latticeai/api/browser.py +0 -493
  224. package/latticeai/api/change_proposals.py +0 -89
  225. package/latticeai/api/chat.py +0 -572
  226. package/latticeai/api/chat_agent_http.py +0 -892
  227. package/latticeai/api/chat_contracts.py +0 -73
  228. package/latticeai/api/chat_documents.py +0 -276
  229. package/latticeai/api/chat_helpers.py +0 -460
  230. package/latticeai/api/chat_history.py +0 -101
  231. package/latticeai/api/chat_hybrid.py +0 -113
  232. package/latticeai/api/chat_intents.py +0 -646
  233. package/latticeai/api/chat_stream.py +0 -216
  234. package/latticeai/api/chronicle.py +0 -63
  235. package/latticeai/api/command_center.py +0 -51
  236. package/latticeai/api/computer_use.py +0 -474
  237. package/latticeai/api/evidence_actions.py +0 -48
  238. package/latticeai/api/features.py +0 -70
  239. package/latticeai/api/funnel_metrics.py +0 -31
  240. package/latticeai/api/garden.py +0 -34
  241. package/latticeai/api/hooks.py +0 -165
  242. package/latticeai/api/index_jobs.py +0 -145
  243. package/latticeai/api/invitations.py +0 -100
  244. package/latticeai/api/knowledge_graph.py +0 -536
  245. package/latticeai/api/marketplace.py +0 -105
  246. package/latticeai/api/mcp.py +0 -482
  247. package/latticeai/api/memory.py +0 -270
  248. package/latticeai/api/network.py +0 -81
  249. package/latticeai/api/network_boundary.py +0 -225
  250. package/latticeai/api/permission_mode.py +0 -61
  251. package/latticeai/api/permissions.py +0 -436
  252. package/latticeai/api/plugins.py +0 -126
  253. package/latticeai/api/portability.py +0 -391
  254. package/latticeai/api/project_sessions.py +0 -114
  255. package/latticeai/api/realtime.py +0 -118
  256. package/latticeai/api/review_queue.py +0 -364
  257. package/latticeai/api/security_dashboard.py +0 -604
  258. package/latticeai/api/setup.py +0 -319
  259. package/latticeai/api/static_routes.py +0 -354
  260. package/latticeai/api/ui_redirects.py +0 -26
  261. package/latticeai/api/workflow_designer.py +0 -394
  262. package/latticeai/api/workspace.py +0 -856
  263. package/latticeai/api/workspace_scope.py +0 -125
  264. package/latticeai/core/agent/__init__.py +0 -93
  265. package/latticeai/core/agent/_contract.py +0 -79
  266. package/latticeai/core/agent/context.py +0 -57
  267. package/latticeai/core/agent/deps.py +0 -125
  268. package/latticeai/core/agent/execution.py +0 -622
  269. package/latticeai/core/agent/planning.py +0 -145
  270. package/latticeai/core/agent/recovery.py +0 -157
  271. package/latticeai/core/agent/runtime.py +0 -210
  272. package/latticeai/core/agent/verification.py +0 -231
  273. package/latticeai/core/agent_eval.py +0 -739
  274. package/latticeai/core/agent_helpers.py +0 -493
  275. package/latticeai/core/agent_profiles.py +0 -110
  276. package/latticeai/core/agent_prompts.py +0 -171
  277. package/latticeai/core/agent_registry.py +0 -232
  278. package/latticeai/core/agent_state.py +0 -41
  279. package/latticeai/core/agent_trace.py +0 -104
  280. package/latticeai/core/artifact_ledger.py +0 -109
  281. package/latticeai/core/audit.py +0 -260
  282. package/latticeai/core/builtin_hooks.py +0 -105
  283. package/latticeai/core/context_builder.py +0 -394
  284. package/latticeai/core/document_generator.py +0 -103
  285. package/latticeai/core/enterprise.py +0 -154
  286. package/latticeai/core/enterprise_admin.py +0 -158
  287. package/latticeai/core/file_generation/__init__.py +0 -115
  288. package/latticeai/core/file_generation/bundles.py +0 -76
  289. package/latticeai/core/file_generation/extraction.py +0 -154
  290. package/latticeai/core/file_generation/inference.py +0 -235
  291. package/latticeai/core/file_generation/orchestration.py +0 -152
  292. package/latticeai/core/file_generation/prompting.py +0 -117
  293. package/latticeai/core/file_generation/repair.py +0 -114
  294. package/latticeai/core/file_generation/sanitize.py +0 -61
  295. package/latticeai/core/file_generation/validation.py +0 -201
  296. package/latticeai/core/invitations.py +0 -132
  297. package/latticeai/core/legacy_compatibility.py +0 -243
  298. package/latticeai/core/logging_safety.py +0 -46
  299. package/latticeai/core/marketplace.py +0 -293
  300. package/latticeai/core/mcp_catalog.py +0 -452
  301. package/latticeai/core/mcp_registry.py +0 -506
  302. package/latticeai/core/network_boundary.py +0 -168
  303. package/latticeai/core/oidc.py +0 -208
  304. package/latticeai/core/plugins.py +0 -432
  305. package/latticeai/core/product_hardening.py +0 -218
  306. package/latticeai/core/project_sessions.py +0 -337
  307. package/latticeai/core/realtime.py +0 -238
  308. package/latticeai/core/run_explain.py +0 -426
  309. package/latticeai/core/run_store.py +0 -252
  310. package/latticeai/core/timezones.py +0 -80
  311. package/latticeai/core/workspace_computer_memory.py +0 -84
  312. package/latticeai/core/workspace_graph_trace.py +0 -155
  313. package/latticeai/core/workspace_indexing.py +0 -102
  314. package/latticeai/core/workspace_memory.py +0 -77
  315. package/latticeai/core/workspace_onboarding.py +0 -104
  316. package/latticeai/core/workspace_os.py +0 -978
  317. package/latticeai/core/workspace_os_constants.py +0 -126
  318. package/latticeai/core/workspace_os_state.py +0 -180
  319. package/latticeai/core/workspace_os_utils.py +0 -103
  320. package/latticeai/core/workspace_permissions.py +0 -101
  321. package/latticeai/core/workspace_plugins.py +0 -97
  322. package/latticeai/core/workspace_relationships.py +0 -99
  323. package/latticeai/core/workspace_reorganization.py +0 -335
  324. package/latticeai/core/workspace_review_items.py +0 -112
  325. package/latticeai/core/workspace_runs.py +0 -726
  326. package/latticeai/core/workspace_skills.py +0 -109
  327. package/latticeai/core/workspace_snapshots.py +0 -198
  328. package/latticeai/core/workspace_timeline.py +0 -110
  329. package/latticeai/integrations/__init__.py +0 -0
  330. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  331. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  332. package/latticeai/integrations/telegram_bot/config.py +0 -86
  333. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  334. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  335. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  336. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  337. package/latticeai/runtime/audit_runtime.py +0 -76
  338. package/latticeai/runtime/automation_runtime.py +0 -81
  339. package/latticeai/runtime/chat_wiring.py +0 -141
  340. package/latticeai/runtime/context_runtime.py +0 -66
  341. package/latticeai/runtime/feature_toggle_wiring.py +0 -157
  342. package/latticeai/runtime/history_runtime.py +0 -163
  343. package/latticeai/runtime/history_writer.py +0 -138
  344. package/latticeai/runtime/hooks_runtime.py +0 -77
  345. package/latticeai/runtime/model_wiring.py +0 -68
  346. package/latticeai/runtime/namespace_runtime.py +0 -163
  347. package/latticeai/runtime/network_boundary_wiring.py +0 -117
  348. package/latticeai/runtime/network_config_runtime.py +0 -56
  349. package/latticeai/runtime/permission_mode_wiring.py +0 -112
  350. package/latticeai/runtime/persistence_runtime.py +0 -159
  351. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  352. package/latticeai/runtime/review_wiring.py +0 -42
  353. package/latticeai/runtime/router_registration.py +0 -693
  354. package/latticeai/runtime/service_singletons.py +0 -55
  355. package/latticeai/runtime/sso_config_runtime.py +0 -128
  356. package/latticeai/runtime/user_key_runtime.py +0 -106
  357. package/latticeai/runtime/web_runtime.py +0 -92
  358. package/latticeai/server_app.py +0 -51
  359. package/latticeai/services/app_context.py +0 -130
  360. package/latticeai/services/automation_execution.py +0 -266
  361. package/latticeai/services/automation_intelligence.py +0 -614
  362. package/latticeai/services/brain_automation.py +0 -191
  363. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  364. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  365. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  366. package/latticeai/services/brain_intelligence/constants.py +0 -47
  367. package/latticeai/services/brain_intelligence/digest.py +0 -258
  368. package/latticeai/services/brain_intelligence/health.py +0 -331
  369. package/latticeai/services/brain_intelligence/proposals.py +0 -259
  370. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  371. package/latticeai/services/brain_intelligence/service.py +0 -48
  372. package/latticeai/services/change_proposals.py +0 -471
  373. package/latticeai/services/chat_service.py +0 -243
  374. package/latticeai/services/chronicle.py +0 -555
  375. package/latticeai/services/cloud_egress_audit.py +0 -85
  376. package/latticeai/services/cloud_extraction.py +0 -129
  377. package/latticeai/services/cloud_streaming.py +0 -268
  378. package/latticeai/services/cloud_token_guard.py +0 -84
  379. package/latticeai/services/command_center.py +0 -548
  380. package/latticeai/services/evidence_actions.py +0 -258
  381. package/latticeai/services/feature_toggles.py +0 -502
  382. package/latticeai/services/folder_watch.py +0 -520
  383. package/latticeai/services/funnel_metrics.py +0 -307
  384. package/latticeai/services/hybrid_chat.py +0 -316
  385. package/latticeai/services/hybrid_context.py +0 -228
  386. package/latticeai/services/hybrid_policy.py +0 -129
  387. package/latticeai/services/interop_bridges.py +0 -978
  388. package/latticeai/services/local_knowledge.py +0 -465
  389. package/latticeai/services/memory_service/__init__.py +0 -52
  390. package/latticeai/services/memory_service/_contract.py +0 -100
  391. package/latticeai/services/memory_service/brief.py +0 -431
  392. package/latticeai/services/memory_service/constants.py +0 -57
  393. package/latticeai/services/memory_service/maintenance.py +0 -138
  394. package/latticeai/services/memory_service/manager.py +0 -186
  395. package/latticeai/services/memory_service/proof.py +0 -136
  396. package/latticeai/services/memory_service/recall.py +0 -225
  397. package/latticeai/services/memory_service/service.py +0 -48
  398. package/latticeai/services/memory_service/stores.py +0 -110
  399. package/latticeai/services/mode_store.py +0 -132
  400. package/latticeai/services/model_recommendation.py +0 -224
  401. package/latticeai/services/model_runtime/cloud.py +0 -87
  402. package/latticeai/services/network_boundary_service.py +0 -117
  403. package/latticeai/services/obsidian_bridge.py +0 -609
  404. package/latticeai/services/openai_compatible_adapter.py +0 -101
  405. package/latticeai/services/permission_mode_service.py +0 -122
  406. package/latticeai/services/platform_runtime.py +0 -366
  407. package/latticeai/services/review_queue.py +0 -380
  408. package/latticeai/services/router_context.py +0 -59
  409. package/latticeai/services/run_executor.py +0 -387
  410. package/latticeai/services/self_model_service.py +0 -171
  411. package/latticeai/services/setup_detection.py +0 -147
  412. package/latticeai/services/triggers.py +0 -378
  413. package/latticeai/services/upload_service.py +0 -172
  414. package/latticeai/services/workspace_service.py +0 -165
  415. package/latticeai/setup/__init__.py +0 -25
  416. package/latticeai/setup/auto_setup.py +0 -846
  417. package/latticeai/setup/demo_corpus.py +0 -98
  418. package/latticeai/setup/wizard/__init__.py +0 -126
  419. package/latticeai/setup/wizard/catalog.py +0 -175
  420. package/latticeai/setup/wizard/detect.py +0 -302
  421. package/latticeai/setup/wizard/install.py +0 -348
  422. package/latticeai/setup/wizard/paths.py +0 -165
  423. package/latticeai/setup/wizard/plans.py +0 -74
  424. package/latticeai/setup/wizard/recommend.py +0 -320
  425. package/scripts/bench_agent_smoke.py +0 -409
  426. package/scripts/bench_models.py +0 -540
  427. package/scripts/bench_vector_index.py +0 -295
  428. package/scripts/funnel_soft_gate.py +0 -192
  429. package/scripts/generate_agent_loop_fixtures.py +0 -994
  430. package/scripts/generate_rust_parity_fixtures.py +0 -908
  431. package/scripts/migrate_brain_storage.py +0 -57
  432. package/scripts/parity_fixture_corpus_context.py +0 -162
  433. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  434. package/scripts/profile_kg.py +0 -355
  435. package/server.py +0 -30
  436. package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
  437. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  438. package/static/app/assets/index-DxmOfNRi.css +0 -2
  439. package/static/app/assets/search-BcHqkjoy.js +0 -1
@@ -1,488 +0,0 @@
1
- """The hybrid retrieval pipeline: keyword + vector + graph expansion.
2
-
3
- One ranking algorithm, kept in one file on purpose — the standing reason
4
- recorded in ``pyproject.toml`` for this file's complexity ignores is that
5
- splitting ``hybrid_search`` across files would scatter the pipeline without
6
- making it clearer. Moved verbatim out of ``retrieval.py`` (v11.3.0).
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import TYPE_CHECKING, Sequence
12
-
13
- # C901: `hybrid_search` is one ranking algorithm at complexity 50. The standing
14
- # reason recorded for it in pyproject.toml is that splitting the pipeline across
15
- # files would scatter it without making it clearer; the ignore rides with the
16
- # file now that the file is the pipeline.
17
- # ruff: noqa: C901,F403,F405
18
- from .._kg_common import * # noqa: F403,F401
19
- from ..fusion import (
20
- DEFAULT_EXPANSION_CAP,
21
- DEFAULT_EXPANSION_SEEDS,
22
- expand_with_neighbors,
23
- graph_expansion_enabled,
24
- rrf_fuse,
25
- )
26
- from .signals import multimodal_signal
27
-
28
- # The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
29
- # `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
30
- # is `object`, so the MRO of `KnowledgeGraphStore` is unchanged. The alias here
31
- # reaches one step further: this half calls `self.search`, which the sibling
32
- # half in .graph_view owns and the composed mixin puts on the same instance.
33
- # Naming that sibling as the typing base states the assumption instead of
34
- # re-declaring its signature where it could drift.
35
- if TYPE_CHECKING:
36
- from .graph_view import _GraphViewMixin as _Core
37
- else:
38
- _Core = object
39
-
40
-
41
- class _HybridSearchMixin(_Core):
42
- """Fused keyword/vector/graph retrieval. Composed into the public mixin."""
43
-
44
- def hybrid_search(
45
- self,
46
- query: str,
47
- *,
48
- top_k: int = 20,
49
- alpha: Optional[float] = None,
50
- workspace_id: Optional[str] = None,
51
- allowed_workspaces=None,
52
- include_legacy_global: bool = False,
53
- lexical_limit: Optional[int] = None,
54
- vector_limit: Optional[int] = None,
55
- min_vector_score: float = 0.0,
56
- image_vector: Optional[Sequence[float]] = None,
57
- image_fusion_weight: Optional[float] = None,
58
- ) -> Dict[str, Any]:
59
- """Unified lexical + vector retrieval with alpha-weighted linear fusion.
60
-
61
- Runs the SQLite lexical :meth:`search` and the embedding-backed
62
- ``vector_search`` (sibling mixin via the store MRO), normalizes both
63
- score spaces to ``[0, 1]``, fuses them as
64
- ``alpha * vector + (1 - alpha) * lexical`` (the same shape as
65
- ``lattice_brain.quality.HybridFusion`` — reimplemented here without
66
- importing that module), and dedupes by ``node_id`` (chunk hits roll up
67
- to their parent node).
68
-
69
- Degrades gracefully: when the vector side is unavailable (mixin not
70
- composed, embedder/index failure) the result falls back to
71
- lexical-only ranking and reports ``mode: "lexical_only"`` with a
72
- ``detail`` explaining why. Each match carries per-source ``scores``
73
- and a ``fusion`` field (``lexical`` / ``vector`` / ``both``).
74
-
75
- ``workspace_id`` is a convenience for single-workspace callers; the
76
- richer ``allowed_workspaces`` set wins when both are provided.
77
-
78
- ``alpha=None`` (the default) resolves the vector share from the
79
- single retrieval policy (:mod:`lattice_brain.graph.retrieval_policy`,
80
- which wraps the query-class fusion table): fact 0.6 (the historical
81
- default) / code 0.35 / person 0.45 / recency 0.5, config-overridable
82
- via ``LATTICEAI_FUSION_WEIGHTS``. The policy also supplies a
83
- deterministic rule-based query rewrite (echoed additively under
84
- ``"policy"``; the response ``"query"`` stays the original) and, for
85
- the ``recency`` class only, an age-decay half-life that dampens each
86
- fused score into the ``[0.5, 1.0]`` band (``scores.age_decay``).
87
- Passing an explicit ``alpha`` pins it exactly as before and disables
88
- rewrite + decay.
89
-
90
- ``image_vector`` (v11.1.0) is the *late fusion* seam for the separate
91
- image space: the caller supplies a query vector from the same vision
92
- model that embedded the pictures, its own index is ranked
93
- independently, and only then are the two rankings blended
94
- (``image_fusion_weight``, default 0.5). A text query never produces
95
- one — it reaches images through their OCR text and captions — which is
96
- exactly why the image channel has to enter at the end rather than
97
- pretending to share the text index.
98
- """
99
- query = str(query or "").strip()
100
- try:
101
- top_k = int(top_k)
102
- except (TypeError, ValueError):
103
- top_k = 20
104
- top_k = max(1, min(top_k, 100))
105
- query_class: Optional[str] = None
106
- search_query = query
107
- rewrite_rules: List[str] = []
108
- recency_half_life_days: Optional[float] = None
109
- # "alpha" is the historical linear fusion; the policy may select RRF
110
- # per query class. An explicitly pinned ``alpha`` argument means the
111
- # caller is asking for linear fusion by name, so it stays linear.
112
- fusion_strategy = "alpha"
113
- if alpha is None:
114
- try:
115
- from ..retrieval_policy import resolve_policy
116
-
117
- policy = resolve_policy(query)
118
- query_class = policy["query_class"]
119
- alpha = float(policy["alpha"])
120
- fusion_strategy = str(policy.get("fusion_strategy") or "alpha")
121
- rewrite_rules = list(policy.get("rewrite_rules") or [])
122
- rewritten = str(policy.get("search_query") or "")
123
- if rewritten and rewritten != query:
124
- search_query = rewritten
125
- half_life = policy.get("recency_half_life_days")
126
- if half_life is not None:
127
- recency_half_life_days = float(half_life)
128
- except Exception: # noqa: BLE001 — policy resolution must never break search
129
- alpha = 0.6
130
- try:
131
- alpha = float(alpha)
132
- except (TypeError, ValueError):
133
- alpha = 0.6
134
- alpha = max(0.0, min(alpha, 1.0))
135
- if allowed_workspaces is None and workspace_id:
136
- allowed_workspaces = {str(workspace_id)}
137
-
138
- if not query:
139
- return {
140
- "query": query,
141
- "mode": "hybrid",
142
- "alpha": alpha,
143
- "query_class": query_class,
144
- "top_k": top_k,
145
- "sources": {"lexical": 0, "vector": 0},
146
- "matches": [],
147
- "policy": {"search_query": search_query, "rewrite_rules": rewrite_rules},
148
- "fusion_strategy": fusion_strategy,
149
- "detail": None,
150
- }
151
-
152
- lex_fetch = max(1, min(int(lexical_limit or max(top_k * 2, 20)), 100))
153
- vec_fetch = max(1, min(int(vector_limit or max(top_k * 2, 20)), 100))
154
-
155
- lexical_matches = self.search(
156
- search_query,
157
- lex_fetch,
158
- allowed_workspaces=allowed_workspaces,
159
- include_legacy_global=include_legacy_global,
160
- ).get("matches", [])
161
-
162
- mode = "hybrid"
163
- detail: Optional[str] = None
164
- vector_matches: List[Dict[str, Any]] = []
165
- vector_recall: Optional[Dict[str, Any]] = None
166
- # The vector channel's own honesty block, echoed additively so a
167
- # caller can tell an exact "not found" from an approximate one.
168
- vector_meta: Dict[str, Any] = {
169
- "backend": None,
170
- "approx": None,
171
- "exhaustive": None,
172
- "truncated": None,
173
- "embedded_rows": None,
174
- "degraded": None,
175
- }
176
- vector_fn = getattr(self, "vector_search", None)
177
- if not callable(vector_fn):
178
- mode = "lexical_only"
179
- detail = "vector search is not available on this store"
180
- else:
181
- try:
182
- vector_payload = (
183
- vector_fn(search_query, limit=vec_fetch, min_score=min_vector_score)
184
- or {}
185
- )
186
- vector_matches = list(vector_payload.get("matches", []))
187
- # Partial recall must reach the caller: the vector channel can
188
- # only score a capped slice of a large index (see
189
- # retrieval_vector.vector_search), and a fused answer built on
190
- # a truncated scan is not the same claim as a complete one.
191
- recall = vector_payload.get("recall")
192
- if isinstance(recall, dict):
193
- vector_meta["backend"] = recall.get("backend")
194
- vector_meta["truncated"] = bool(recall.get("truncated"))
195
- vector_meta["embedded_rows"] = recall.get("candidates_total")
196
- if recall.get("truncated"):
197
- vector_recall = dict(recall)
198
- index_block = vector_payload.get("index")
199
- if isinstance(index_block, dict):
200
- vector_meta["approx"] = bool(index_block.get("approx"))
201
- vector_meta["exhaustive"] = bool(index_block.get("exhaustive"))
202
- except Exception as exc: # noqa: BLE001 — degrade, never fail the search
203
- mode = "lexical_only"
204
- detail = f"vector index unavailable: {exc}"
205
- vector_matches = []
206
- # An embedder swap makes the vector channel silently return zero rows
207
- # (vector_search filters on the CURRENT model/dim). Surface the honest
208
- # cause additively without changing the mode string.
209
- vector_degraded: Optional[str] = None
210
- if mode == "hybrid" and not vector_matches:
211
- try:
212
- fingerprint_fn = getattr(self, "embedder_fingerprint_status", None)
213
- if callable(fingerprint_fn) and fingerprint_fn().get("stale_embedder"):
214
- vector_degraded = "stale_embedder"
215
- except Exception: # noqa: BLE001 — fingerprint status must never break search
216
- vector_degraded = None
217
- if vector_matches and allowed_workspaces is not None:
218
- vector_matches = self.filter_scoped_nodes(
219
- vector_matches,
220
- allowed_workspaces,
221
- id_key="node_id",
222
- include_legacy_global=include_legacy_global,
223
- )
224
-
225
- def _parent_node_id(match: Dict[str, Any]) -> str:
226
- # Chunk-level hits dedupe to their parent content node.
227
- if match.get("type") == "Chunk":
228
- meta = match.get("metadata") or {}
229
- parent = meta.get("source_node") or meta.get("parent_source_node")
230
- if parent:
231
- return str(parent)
232
- return str(match.get("node_id") or match.get("id") or "")
233
-
234
- entries: Dict[str, Dict[str, Any]] = {}
235
-
236
- def _entry_for(node_id: str, match: Dict[str, Any]) -> Dict[str, Any]:
237
- entry = entries.get(node_id)
238
- if entry is None:
239
- entry = {
240
- "node_id": node_id,
241
- "id": match.get("id") or node_id,
242
- "type": match.get("type"),
243
- "title": match.get("title"),
244
- "summary": match.get("summary"),
245
- "metadata": match.get("metadata") or {},
246
- "updated_at": match.get("updated_at"),
247
- "scores": {"lexical": 0.0, "vector": 0.0},
248
- "_lexical": False,
249
- "_vector": False,
250
- }
251
- entries[node_id] = entry
252
- return entry
253
-
254
- # Per-channel id order (best first) — the only input RRF needs, and
255
- # the one thing a normalized score cannot reconstruct.
256
- lexical_order: List[str] = []
257
- vector_order: List[str] = []
258
-
259
- for rank, match in enumerate(lexical_matches, start=1):
260
- node_id = _parent_node_id(match)
261
- if not node_id:
262
- continue
263
- entry = _entry_for(node_id, match)
264
- entry["scores"]["lexical"] = max(
265
- entry["scores"]["lexical"], round(1.0 / rank, 6)
266
- )
267
- entry["_lexical"] = True
268
- lexical_order.append(node_id)
269
-
270
- # Max-normalize cosine scores into [0, 1] (guard the score-0 falsy trap
271
- # by comparing explicitly, never with truthiness).
272
- max_vec = 0.0
273
- for match in vector_matches:
274
- raw = match.get("score")
275
- if raw is not None and float(raw) > max_vec:
276
- max_vec = float(raw)
277
- for match in vector_matches:
278
- node_id = _parent_node_id(match)
279
- if not node_id:
280
- continue
281
- raw = float(match.get("score") or 0.0)
282
- vec_norm = max(0.0, raw) / max_vec if max_vec > 0 else 0.0
283
- entry = _entry_for(node_id, match)
284
- entry["scores"]["vector"] = max(entry["scores"]["vector"], round(vec_norm, 6))
285
- entry["_vector"] = True
286
- vector_order.append(node_id)
287
- # Prefer a real snippet when the lexical row had no summary.
288
- if not entry.get("summary") and match.get("summary"):
289
- entry["summary"] = match.get("summary")
290
-
291
- # Graph traversal candidate expansion (opt-in, capped, counted): pull
292
- # the one-hop neighbours of the strongest hits into the candidate pool
293
- # so an answer that is adjacent to the match — not in it — is
294
- # reachable at all. Off by default; see fusion.GRAPH_EXPANSION_ENV.
295
- expansion_report: Dict[str, Any] = {
296
- "enabled": False,
297
- "seeds": 0,
298
- "added": 0,
299
- "cap": DEFAULT_EXPANSION_CAP,
300
- "truncated": False,
301
- "failed_seeds": 0,
302
- }
303
- if entries and graph_expansion_enabled():
304
- seeds = sorted(
305
- (
306
- (node_id, float(entry["scores"]["vector"]))
307
- for node_id, entry in entries.items()
308
- ),
309
- key=lambda pair: -pair[1],
310
- )[:DEFAULT_EXPANSION_SEEDS]
311
- expanded, expansion_report = expand_with_neighbors(
312
- seeds,
313
- self.neighbors,
314
- exclude=list(entries),
315
- cap=DEFAULT_EXPANSION_CAP,
316
- )
317
- for candidate in expanded:
318
- node = candidate["node"]
319
- entry = _entry_for(str(node.get("id")), dict(node))
320
- entry["scores"]["graph"] = candidate["score"]
321
- entry["metadata"] = {
322
- **(entry.get("metadata") or {}),
323
- "expanded_from": candidate["seed"],
324
- }
325
- entry["_graph"] = True
326
-
327
- rrf_normalized: Dict[str, float] = {}
328
- if fusion_strategy == "rrf":
329
- raw_rrf = rrf_fuse(
330
- {
331
- "lexical": list(dict.fromkeys(lexical_order)),
332
- "vector": list(dict.fromkeys(vector_order)),
333
- }
334
- )
335
- peak = max(raw_rrf.values(), default=0.0)
336
- if peak > 0:
337
- # Rescale to [0, 1] so the score column keeps the same meaning
338
- # across strategies; RRF's raw values live around 1/60.
339
- rrf_normalized = {key: value / peak for key, value in raw_rrf.items()}
340
-
341
- matches: List[Dict[str, Any]] = []
342
- for entry in entries.values():
343
- lex_score = float(entry["scores"]["lexical"])
344
- vec_score = float(entry["scores"]["vector"])
345
- if mode == "lexical_only":
346
- fused = lex_score
347
- elif fusion_strategy == "rrf":
348
- fused = float(rrf_normalized.get(entry["node_id"], 0.0))
349
- entry["scores"]["rrf"] = round(fused, 6)
350
- else:
351
- fused = alpha * vec_score + (1.0 - alpha) * lex_score
352
- from_lexical = bool(entry.pop("_lexical", False))
353
- from_vector = bool(entry.pop("_vector", False))
354
- if entry.pop("_graph", False):
355
- # A one-hop neighbour of a hit: related to the answer, never
356
- # itself a match, so it carries only its damped seed score.
357
- fused = float(entry["scores"]["graph"])
358
- entry["fusion"] = "graph"
359
- elif from_lexical and from_vector:
360
- entry["fusion"] = "both"
361
- elif from_vector:
362
- entry["fusion"] = "vector"
363
- else:
364
- entry["fusion"] = "lexical"
365
- entry["score"] = round(fused, 6)
366
- matches.append(entry)
367
-
368
- # Recency-class age decay (retrieval_policy): dampen each fused score
369
- # into the [0.5, 1.0] band so old-but-relevant items sink without ever
370
- # being zeroed. Other classes skip this block byte-identically.
371
- if recency_half_life_days is not None:
372
- decay_now = datetime.now()
373
- for match in matches:
374
- stamp = match.get("updated_at")
375
- if _parse_iso(stamp):
376
- multiplier = 0.5 + 0.5 * _recency_score(
377
- stamp, now=decay_now, half_life_days=recency_half_life_days
378
- )
379
- else:
380
- # Unknown age is not evidence of staleness — never dampen.
381
- multiplier = 1.0
382
- match["scores"]["age_decay"] = round(multiplier, 6)
383
- match["score"] = round(float(match["score"]) * multiplier, 6)
384
-
385
- # Late fusion of the image space (v11.1.0). Runs after the text
386
- # channels have produced a ranking and before the cut, so image
387
- # evidence can lift a picture into the answer without ever having been
388
- # compared against a text vector.
389
- image_fusion: Optional[Dict[str, Any]] = None
390
- if image_vector is not None:
391
- image_fusion = self._fuse_image_channel(
392
- matches, image_vector, top_k=top_k, weight=image_fusion_weight
393
- )
394
-
395
- matches.sort(key=lambda item: (-item["score"], item["node_id"]))
396
- # Optional cross-encoder rerank (v9.9.5). Off by default; when the
397
- # env kill-switch is set and the model loads, pair scores reorder the
398
- # fused list. Failures degrade to identity and never break search.
399
- rerank_meta: Dict[str, Any]
400
- try:
401
- from ..rerank import rerank_matches
402
-
403
- # Rerank a slightly wider window, then cut to top_k.
404
- window = matches[: max(top_k * 2, top_k)]
405
- reranked = rerank_matches(search_query, window, top_k=top_k)
406
- matches = list(reranked.get("matches") or matches[:top_k])
407
- rerank_meta = {
408
- "mode": reranked.get("mode") or "identity",
409
- "model": reranked.get("model"),
410
- "detail": reranked.get("detail"),
411
- }
412
- except Exception as exc: # noqa: BLE001 — rerank must never break search
413
- matches = matches[:top_k]
414
- rerank_meta = {"mode": "identity", "model": None, "detail": str(exc)}
415
- for rank, match in enumerate(matches, start=1):
416
- match["rank"] = rank
417
- result = {
418
- "query": query,
419
- "mode": mode,
420
- "alpha": alpha,
421
- "query_class": query_class,
422
- "top_k": top_k,
423
- "sources": {"lexical": len(lexical_matches), "vector": len(vector_matches)},
424
- "matches": matches,
425
- "policy": {"search_query": search_query, "rewrite_rules": rewrite_rules},
426
- "fusion_strategy": fusion_strategy,
427
- "graph_expansion": expansion_report,
428
- "rerank": rerank_meta,
429
- "detail": detail,
430
- }
431
- if vector_degraded is not None:
432
- result["vector_degraded"] = vector_degraded
433
- if vector_recall is not None:
434
- result["vector_recall"] = vector_recall
435
- if vector_degraded is None:
436
- result["vector_degraded"] = "partial_recall"
437
- vector_meta["degraded"] = result.get("vector_degraded")
438
- result["vector"] = vector_meta
439
- multimodal = multimodal_signal(matches)
440
- if multimodal is not None or image_fusion is not None:
441
- result["multimodal"] = {
442
- **(multimodal or {"images": 0, "types": []}),
443
- **({"image_fusion": image_fusion} if image_fusion is not None else {}),
444
- }
445
- return result
446
-
447
- def _fuse_image_channel(
448
- self,
449
- matches: List[Dict[str, Any]],
450
- image_vector: Sequence[float],
451
- *,
452
- top_k: int,
453
- weight: Optional[float],
454
- ) -> Dict[str, Any]:
455
- """Rank the image index separately, then blend it into ``matches``.
456
-
457
- Any failure degrades to "the image channel contributed nothing" with
458
- the reason attached — an image index that cannot be read is not a
459
- reason to lose the text answer.
460
- """
461
- from ..image_vectors import (
462
- DEFAULT_IMAGE_FUSION_WEIGHT,
463
- fuse_image_scores,
464
- image_similarity_search,
465
- )
466
-
467
- share = DEFAULT_IMAGE_FUSION_WEIGHT if weight is None else float(weight)
468
- report: Dict[str, Any] = {
469
- "weight": round(max(0.0, min(1.0, share)), 4),
470
- "candidates": 0,
471
- "fused": 0,
472
- "detail": None,
473
- }
474
- try:
475
- found = image_similarity_search(
476
- self, image_vector, top_k=max(1, int(top_k) * 2)
477
- )
478
- except Exception as exc: # noqa: BLE001 — never fail the text answer
479
- report["detail"] = f"image index unavailable: {exc}"
480
- return report
481
- report["candidates"] = int(found.get("candidates") or 0)
482
- report["detail"] = found.get("detail")
483
- scores = {
484
- str(row.get("node_id")): float(row.get("score") or 0.0)
485
- for row in found.get("matches") or []
486
- }
487
- report["fused"] = fuse_image_scores(matches, scores, weight=share)
488
- return report
@@ -1,121 +0,0 @@
1
- """Destructive graph maintenance: drop one conversation, or everything.
2
-
3
- Both methods gate their ``nodes_v2`` work on ``KGStoreV2``. That name is
4
- star-imported from ``_kg_common`` into **this** module's globals, so the
5
- patch target for "pretend the v2 projection is unavailable" is
6
- ``lattice_brain.graph.retrieval.maintenance.KGStoreV2``.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import TYPE_CHECKING
12
-
13
- # ruff: noqa: F403,F405
14
- from .._kg_common import * # noqa: F403,F401
15
-
16
- # The cross-mixin surface (`_connect`, `_upsert_node`, …) is declared in
17
- # `_kg_contract.KnowledgeGraphCore`. It is a typing-only base: at runtime this
18
- # is `object`, so the MRO of `KnowledgeGraphStore` is unchanged.
19
- if TYPE_CHECKING:
20
- from .._kg_contract import KnowledgeGraphCore as _Core
21
- else:
22
- _Core = object
23
-
24
-
25
- class _MaintenanceMixin(_Core):
26
- """Conversation/graph deletion. Composed into the public mixin."""
27
-
28
- def delete_conversation(self, conversation_id: str) -> Dict[str, Any]:
29
- conversation_id = str(conversation_id or "").strip()
30
- if not conversation_id:
31
- return {"status": "skipped", "removed_nodes": 0}
32
- conv_id = f"conversation:{_slug(conversation_id)}"
33
- with self._connect() as conn:
34
- # Edge rows may carry the legacy lowercase label (pre-v4) or the
35
- # canonical EdgeType value (v4 write door) — match both.
36
- direct_ids = [
37
- row["to_node"]
38
- for row in conn.execute(
39
- "SELECT to_node FROM edges WHERE from_node=? AND type IN ('contains', 'CONTAINS')",
40
- (conv_id,),
41
- )
42
- ]
43
- remove_ids = set(direct_ids)
44
- child_types = [
45
- "has_chunk",
46
- "implies",
47
- "contains_signal",
48
- "has_page",
49
- "has_slide",
50
- "has_sheet",
51
- "contains_image",
52
- ]
53
- child_types += [t.upper() for t in child_types]
54
- placeholders = ",".join("?" for _ in child_types)
55
- for source_id in list(direct_ids):
56
- for row in conn.execute(
57
- f"SELECT to_node FROM edges WHERE from_node=? AND type IN ({placeholders})",
58
- (source_id, *child_types),
59
- ):
60
- remove_ids.add(row["to_node"])
61
- remove_ids.add(conv_id)
62
- for node_id in remove_ids:
63
- conn.execute("DELETE FROM nodes WHERE id=?", (node_id,))
64
- if KGStoreV2 is not None:
65
- conn.execute(
66
- "DELETE FROM nodes_v2 WHERE id=?", (node_id,)
67
- ) # edges_v2 cascade
68
- conn.execute(
69
- """
70
- DELETE FROM nodes
71
- WHERE type='Topic'
72
- AND id NOT IN (SELECT to_node FROM edges)
73
- AND id NOT IN (SELECT from_node FROM edges)
74
- """
75
- )
76
- if KGStoreV2 is not None:
77
- conn.execute(
78
- """
79
- DELETE FROM nodes_v2
80
- WHERE legacy_type='Topic'
81
- AND id NOT IN (SELECT target FROM edges_v2)
82
- AND id NOT IN (SELECT source FROM edges_v2)
83
- """
84
- )
85
- return {
86
- "status": "ok",
87
- "conversation_id": conversation_id,
88
- "removed_nodes": len(remove_ids),
89
- }
90
-
91
- def clear_all(self) -> Dict[str, Any]:
92
- with self._connect() as conn:
93
- counts = {
94
- "nodes": conn.execute("SELECT COUNT(*) AS c FROM nodes").fetchone()[
95
- "c"
96
- ],
97
- "edges": conn.execute("SELECT COUNT(*) AS c FROM edges").fetchone()[
98
- "c"
99
- ],
100
- "chunks": conn.execute("SELECT COUNT(*) AS c FROM chunks").fetchone()[
101
- "c"
102
- ],
103
- "knowledge_sources": conn.execute(
104
- "SELECT COUNT(*) AS c FROM knowledge_sources"
105
- ).fetchone()["c"],
106
- "local_file_index": conn.execute(
107
- "SELECT COUNT(*) AS c FROM local_file_index"
108
- ).fetchone()["c"],
109
- }
110
- conn.execute("DELETE FROM local_file_index")
111
- conn.execute("DELETE FROM knowledge_sources")
112
- conn.execute("DELETE FROM chunks")
113
- conn.execute("DELETE FROM edges")
114
- conn.execute("DELETE FROM nodes")
115
- if KGStoreV2 is not None:
116
- conn.execute("DELETE FROM edges_v2")
117
- conn.execute("DELETE FROM nodes_v2")
118
- if self.blob_dir.exists():
119
- shutil.rmtree(self.blob_dir, ignore_errors=True)
120
- self.blob_dir.mkdir(parents=True, exist_ok=True)
121
- return {"status": "ok", "removed": counts}