ltcai 11.1.0 → 11.4.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 (289) hide show
  1. package/README.md +47 -54
  2. package/docs/CHANGELOG.md +94 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/FEATURE_AUDIT_v11.2.0.md +393 -0
  6. package/docs/LAYOUT_REBUILD_SPEC.md +9 -1
  7. package/docs/MULTI_AGENT_RUNTIME.md +1 -1
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -2
  10. package/docs/PERMISSION_MODE.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/architecture.md +6 -2
  14. package/docs/kg-schema.md +2 -2
  15. package/docs/v11.3.0_PLAN.md +202 -0
  16. package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +176 -0
  17. package/lattice_brain/__init__.py +1 -1
  18. package/lattice_brain/gates.py +125 -0
  19. package/lattice_brain/graph/_kg_common/__init__.py +287 -0
  20. package/lattice_brain/graph/_kg_common/extraction.py +516 -0
  21. package/lattice_brain/graph/_kg_common/relations.py +161 -0
  22. package/lattice_brain/graph/_kg_common/text.py +479 -0
  23. package/lattice_brain/graph/discovery_index/__init__.py +35 -0
  24. package/lattice_brain/graph/discovery_index/cleanup.py +182 -0
  25. package/lattice_brain/graph/discovery_index/extract.py +137 -0
  26. package/lattice_brain/graph/discovery_index/scan.py +411 -0
  27. package/lattice_brain/graph/discovery_index/upsert.py +495 -0
  28. package/lattice_brain/graph/fusion.py +35 -4
  29. package/lattice_brain/graph/projection/__init__.py +42 -0
  30. package/lattice_brain/graph/projection/curation.py +500 -0
  31. package/lattice_brain/graph/{projection.py → projection/v2_schema.py} +81 -485
  32. package/lattice_brain/graph/retrieval/__init__.py +54 -0
  33. package/lattice_brain/graph/retrieval/context.py +197 -0
  34. package/lattice_brain/graph/retrieval/graph_view.py +319 -0
  35. package/lattice_brain/graph/retrieval/hybrid.py +488 -0
  36. package/lattice_brain/graph/retrieval/maintenance.py +121 -0
  37. package/lattice_brain/graph/retrieval/signals.py +95 -0
  38. package/lattice_brain/graph/retrieval_vector/__init__.py +42 -0
  39. package/lattice_brain/graph/retrieval_vector/fingerprint.py +97 -0
  40. package/lattice_brain/graph/retrieval_vector/indexing.py +347 -0
  41. package/lattice_brain/graph/retrieval_vector/search.py +560 -0
  42. package/lattice_brain/graph/retrieval_vector/status.py +374 -0
  43. package/lattice_brain/graph/schema.py +9 -0
  44. package/lattice_brain/graph/store.py +9 -0
  45. package/lattice_brain/graph/vector_index/selector.py +32 -2
  46. package/lattice_brain/ingestion/__init__.py +130 -0
  47. package/lattice_brain/ingestion/_contract.py +90 -0
  48. package/lattice_brain/ingestion/constants.py +127 -0
  49. package/lattice_brain/ingestion/folder_scan.py +57 -0
  50. package/lattice_brain/ingestion/folders.py +258 -0
  51. package/lattice_brain/ingestion/hashing.py +26 -0
  52. package/lattice_brain/ingestion/jobs_api.py +107 -0
  53. package/lattice_brain/ingestion/models.py +80 -0
  54. package/lattice_brain/ingestion/pipeline.py +486 -0
  55. package/lattice_brain/ingestion/quality.py +209 -0
  56. package/lattice_brain/ingestion/routing.py +295 -0
  57. package/lattice_brain/multimodal/__init__.py +164 -0
  58. package/lattice_brain/multimodal/audio.py +77 -0
  59. package/lattice_brain/multimodal/common.py +118 -0
  60. package/lattice_brain/{multimodal.py → multimodal/images.py} +22 -262
  61. package/lattice_brain/multimodal/ports.py +169 -0
  62. package/lattice_brain/multimodal/video.py +410 -0
  63. package/lattice_brain/portability/__init__.py +90 -0
  64. package/lattice_brain/portability/_contract.py +42 -0
  65. package/lattice_brain/portability/backups.py +338 -0
  66. package/lattice_brain/portability/bundles.py +136 -0
  67. package/lattice_brain/portability/constants.py +93 -0
  68. package/lattice_brain/portability/fsops.py +138 -0
  69. package/lattice_brain/portability/service.py +41 -0
  70. package/lattice_brain/portability/sharing.py +714 -0
  71. package/lattice_brain/runtime/__init__.py +1 -1
  72. package/lattice_brain/runtime/multi_agent.py +1 -1
  73. package/lattice_brain/sealed_box.py +244 -0
  74. package/lattice_brain/synthesis.py +24 -1
  75. package/latticeai/__init__.py +1 -1
  76. package/latticeai/api/brain_intelligence.py +4 -0
  77. package/latticeai/api/chat.py +11 -0
  78. package/latticeai/api/chat_helpers.py +16 -3
  79. package/latticeai/api/chat_hybrid.py +32 -1
  80. package/latticeai/api/chronicle.py +63 -0
  81. package/latticeai/api/features.py +70 -0
  82. package/latticeai/api/local_files.py +102 -0
  83. package/latticeai/api/portability.py +39 -4
  84. package/latticeai/api/review_queue.py +126 -0
  85. package/latticeai/api/search.py +16 -2
  86. package/latticeai/core/agent/__init__.py +93 -0
  87. package/latticeai/core/agent/_contract.py +79 -0
  88. package/latticeai/core/agent/context.py +57 -0
  89. package/latticeai/core/agent/deps.py +125 -0
  90. package/latticeai/core/agent/execution.py +622 -0
  91. package/latticeai/core/agent/planning.py +145 -0
  92. package/latticeai/core/agent/recovery.py +157 -0
  93. package/latticeai/core/agent/runtime.py +210 -0
  94. package/latticeai/core/agent/verification.py +231 -0
  95. package/latticeai/core/config.py +4 -1
  96. package/latticeai/core/context_builder.py +6 -3
  97. package/latticeai/core/embedding_providers/__init__.py +151 -0
  98. package/latticeai/core/embedding_providers/base.py +199 -0
  99. package/latticeai/core/embedding_providers/captions.py +162 -0
  100. package/latticeai/core/embedding_providers/profiles.py +126 -0
  101. package/latticeai/core/embedding_providers/text.py +350 -0
  102. package/latticeai/core/embedding_providers/vision.py +352 -0
  103. package/latticeai/core/file_generation/__init__.py +115 -0
  104. package/latticeai/core/file_generation/bundles.py +76 -0
  105. package/latticeai/core/file_generation/extraction.py +154 -0
  106. package/latticeai/core/file_generation/inference.py +235 -0
  107. package/latticeai/core/file_generation/orchestration.py +152 -0
  108. package/latticeai/core/file_generation/prompting.py +117 -0
  109. package/latticeai/core/file_generation/repair.py +114 -0
  110. package/latticeai/core/file_generation/sanitize.py +61 -0
  111. package/latticeai/core/file_generation/validation.py +201 -0
  112. package/latticeai/core/legacy_compatibility.py +1 -1
  113. package/latticeai/core/marketplace.py +1 -1
  114. package/latticeai/core/messages.py +152 -0
  115. package/latticeai/core/model_compat.py +73 -2
  116. package/latticeai/core/workspace_os_constants.py +1 -1
  117. package/latticeai/integrations/telegram_bot/__init__.py +123 -0
  118. package/latticeai/integrations/telegram_bot/__main__.py +17 -0
  119. package/latticeai/integrations/telegram_bot/config.py +86 -0
  120. package/latticeai/integrations/telegram_bot/dispatch.py +311 -0
  121. package/latticeai/integrations/telegram_bot/flows.py +478 -0
  122. package/latticeai/integrations/telegram_bot/helpers.py +322 -0
  123. package/latticeai/integrations/telegram_bot/screens.py +394 -0
  124. package/latticeai/models/model_providers.py +12 -4
  125. package/latticeai/models/router/__init__.py +88 -0
  126. package/latticeai/models/router/_contract.py +66 -0
  127. package/latticeai/models/router/branding.py +56 -0
  128. package/latticeai/models/router/catalog.py +69 -0
  129. package/latticeai/models/router/documents.py +199 -0
  130. package/latticeai/models/router/errors.py +37 -0
  131. package/latticeai/models/router/generation.py +258 -0
  132. package/latticeai/models/router/loading.py +291 -0
  133. package/latticeai/models/router/local_models.py +85 -0
  134. package/latticeai/models/router/registry.py +147 -0
  135. package/latticeai/runtime/build_phases/__init__.py +82 -0
  136. package/latticeai/runtime/build_phases/features.py +407 -0
  137. package/latticeai/runtime/build_phases/foundation.py +555 -0
  138. package/latticeai/runtime/build_phases/web.py +492 -0
  139. package/latticeai/runtime/chat_wiring.py +4 -0
  140. package/latticeai/runtime/feature_toggle_wiring.py +163 -0
  141. package/latticeai/runtime/router_registration.py +11 -0
  142. package/latticeai/runtime/runtime_context.py +1 -0
  143. package/latticeai/services/app_context.py +8 -0
  144. package/latticeai/services/architecture_readiness.py +48 -19
  145. package/latticeai/services/automation_intelligence.py +22 -2
  146. package/latticeai/services/brain_intelligence/__init__.py +58 -0
  147. package/latticeai/services/brain_intelligence/_contract.py +71 -0
  148. package/latticeai/services/brain_intelligence/consistency.py +193 -0
  149. package/latticeai/services/brain_intelligence/constants.py +47 -0
  150. package/latticeai/services/brain_intelligence/digest.py +258 -0
  151. package/latticeai/services/brain_intelligence/health.py +331 -0
  152. package/latticeai/services/brain_intelligence/proposals.py +264 -0
  153. package/latticeai/services/brain_intelligence/sampling.py +84 -0
  154. package/latticeai/services/brain_intelligence/service.py +48 -0
  155. package/latticeai/services/chronicle.py +557 -0
  156. package/latticeai/services/command_center.py +10 -4
  157. package/latticeai/services/feature_toggles.py +502 -0
  158. package/latticeai/services/folder_watch.py +122 -1
  159. package/latticeai/services/hybrid_chat.py +56 -5
  160. package/latticeai/services/interop_bridges.py +978 -0
  161. package/latticeai/services/memory_service/__init__.py +52 -0
  162. package/latticeai/services/memory_service/_contract.py +100 -0
  163. package/latticeai/services/memory_service/brief.py +431 -0
  164. package/latticeai/services/memory_service/constants.py +57 -0
  165. package/latticeai/services/memory_service/maintenance.py +138 -0
  166. package/latticeai/services/memory_service/manager.py +186 -0
  167. package/latticeai/services/memory_service/proof.py +136 -0
  168. package/latticeai/services/memory_service/recall.py +225 -0
  169. package/latticeai/services/memory_service/service.py +48 -0
  170. package/latticeai/services/memory_service/stores.py +110 -0
  171. package/latticeai/services/model_capability_registry.py +434 -261
  172. package/latticeai/services/model_catalog.py +95 -61
  173. package/latticeai/services/model_recommendation.py +18 -11
  174. package/latticeai/services/model_runtime/__init__.py +322 -0
  175. package/latticeai/services/model_runtime/cloud.py +87 -0
  176. package/latticeai/services/model_runtime/download.py +282 -0
  177. package/latticeai/services/model_runtime/engines.py +341 -0
  178. package/latticeai/services/model_runtime/loading.py +178 -0
  179. package/latticeai/services/model_runtime/service.py +129 -0
  180. package/latticeai/services/model_runtime/state.py +131 -0
  181. package/latticeai/services/model_runtime/status.py +255 -0
  182. package/latticeai/services/multimodal_ports.py +26 -1
  183. package/latticeai/services/obsidian_bridge.py +16 -25
  184. package/latticeai/services/product_readiness.py +15 -7
  185. package/latticeai/services/search_service.py +149 -2
  186. package/latticeai/services/tool_dispatch.py +4 -0
  187. package/latticeai/setup/auto_setup.py +27 -30
  188. package/latticeai/setup/wizard/__init__.py +126 -0
  189. package/latticeai/setup/wizard/catalog.py +172 -0
  190. package/latticeai/setup/wizard/detect.py +323 -0
  191. package/latticeai/setup/wizard/install.py +348 -0
  192. package/latticeai/setup/wizard/paths.py +168 -0
  193. package/latticeai/setup/wizard/plans.py +74 -0
  194. package/latticeai/setup/wizard/recommend.py +320 -0
  195. package/package.json +6 -2
  196. package/scripts/bump_version.py +14 -0
  197. package/scripts/capture_release_evidence.mjs +33 -21
  198. package/scripts/check_current_release_docs.mjs +1 -1
  199. package/scripts/check_i18n_namespace_coverage.mjs +41 -4
  200. package/scripts/check_max_file_lines.mjs +102 -0
  201. package/scripts/check_release_evidence_bound.mjs +30 -15
  202. package/scripts/check_screenshot_pixel_delta.py +34 -4
  203. package/scripts/check_server_i18n.mjs +2 -0
  204. package/scripts/generate_rust_parity_fixtures.py +562 -0
  205. package/scripts/lib/mock_server_fingerprint.mjs +94 -0
  206. package/scripts/release_screen_claims.json +44 -2
  207. package/scripts/verify_hf_model_registry.py +253 -218
  208. package/src-tauri/Cargo.lock +361 -3
  209. package/src-tauri/Cargo.toml +6 -1
  210. package/src-tauri/src/backend.rs +349 -0
  211. package/src-tauri/src/folder.rs +33 -0
  212. package/src-tauri/src/main.rs +97 -399
  213. package/src-tauri/tauri.conf.json +1 -1
  214. package/static/app/asset-manifest.json +41 -37
  215. package/static/app/assets/Act-yYpYnn0v.js +1 -0
  216. package/static/app/assets/AdminConsole-DL3Cr5pL.js +1 -0
  217. package/static/app/assets/{Brain-CzCsI1mi.js → Brain-C1HBN0Wf.js} +2 -2
  218. package/static/app/assets/BrainHome-DoXRhUUC.js +2 -0
  219. package/static/app/assets/BrainSignals-6yR6ir5t.js +1 -0
  220. package/static/app/assets/Capture-CFIRsFNE.js +1 -0
  221. package/static/app/assets/Chronicle-BZbEgiwN.js +1 -0
  222. package/static/app/assets/CommandPalette-D2pMxC2I.js +1 -0
  223. package/static/app/assets/Library-DwO3yZST.js +1 -0
  224. package/static/app/assets/{LivingBrain-BXMWIK_2.js → LivingBrain-Jn1GK0-S.js} +1 -1
  225. package/static/app/assets/ProductFlow-B-w1R4Oo.js +1 -0
  226. package/static/app/assets/ReviewCard-6B27X8Vg.js +3 -0
  227. package/static/app/assets/System-DW8F-2xL.js +1 -0
  228. package/static/app/assets/arrow-left-DXvKg9U6.js +1 -0
  229. package/static/app/assets/{bot-4BvN07ux.js → bot-IM_E_Y12.js} +1 -1
  230. package/static/app/assets/brain-Ci1CkWjM.js +1 -0
  231. package/static/app/assets/{button-CDjtnAoU.js → button-COwyqfHM.js} +1 -1
  232. package/static/app/assets/circle-check-DfInj-qD.js +1 -0
  233. package/static/app/assets/{circle-pause-D_RMn7tp.js → circle-pause-DEM4A1Y5.js} +1 -1
  234. package/static/app/assets/{circle-play-B5OpB8ae.js → circle-play-C9djDuLd.js} +1 -1
  235. package/static/app/assets/{cpu-BIlWInHf.js → cpu-DFdo1gw-.js} +1 -1
  236. package/static/app/assets/{download-BtjXfL3z.js → download-SnJL6oqk.js} +1 -1
  237. package/static/app/assets/{folder-open-DefMpxI2.js → folder-open-CqZeDkjE.js} +1 -1
  238. package/static/app/assets/{hard-drive-BQ8NZVkw.js → hard-drive-j1jJXYYf.js} +1 -1
  239. package/static/app/assets/{index-vtEfYvQY.css → index-BLPb5lmE.css} +1 -1
  240. package/static/app/assets/index-_u5iUHDr.js +10 -0
  241. package/static/app/assets/input-B0lPdRQZ.js +1 -0
  242. package/static/app/assets/link-2-CoFbooHS.js +1 -0
  243. package/static/app/assets/{permissionCopy-BqZ5tsgL.js → permissionCopy-BsyLxtao.js} +1 -1
  244. package/static/app/assets/primitives-DEbN-d6p.js +1 -0
  245. package/static/app/assets/search-BybIWPNd.js +1 -0
  246. package/static/app/assets/{share-2-D5zg_0fY.js → share-2-CVtZ_ewX.js} +1 -1
  247. package/static/app/assets/{shield-alert-B5pZzkUb.js → shield-alert-CBi2GNWM.js} +1 -1
  248. package/static/app/assets/{textarea-nEVIweKY.js → textarea-DNMpB5ih.js} +1 -1
  249. package/static/app/assets/{useFocusTrap-Cm99AHlz.js → useFocusTrap-C83t3GXF.js} +1 -1
  250. package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
  251. package/static/app/assets/{useQuery-Dm__N6bL.js → useQuery-Dcp1OChy.js} +1 -1
  252. package/static/app/assets/utils-BlZr7Pd4.js +4 -0
  253. package/static/app/assets/workspace-jJY4RuAV.js +1 -0
  254. package/static/app/index.html +4 -4
  255. package/static/sw.js +1 -1
  256. package/lattice_brain/graph/_kg_common.py +0 -1331
  257. package/lattice_brain/graph/discovery_index.py +0 -1141
  258. package/lattice_brain/graph/retrieval.py +0 -1120
  259. package/lattice_brain/graph/retrieval_vector.py +0 -1293
  260. package/lattice_brain/ingestion.py +0 -1377
  261. package/lattice_brain/portability.py +0 -1210
  262. package/latticeai/core/agent.py +0 -1412
  263. package/latticeai/core/embedding_providers.py +0 -1196
  264. package/latticeai/core/file_generation.py +0 -1047
  265. package/latticeai/integrations/telegram_bot.py +0 -1390
  266. package/latticeai/models/router.py +0 -1007
  267. package/latticeai/runtime/build_phases.py +0 -1422
  268. package/latticeai/services/brain_intelligence.py +0 -967
  269. package/latticeai/services/memory_service.py +0 -1177
  270. package/latticeai/services/model_runtime.py +0 -1281
  271. package/latticeai/setup/wizard.py +0 -1277
  272. package/static/app/assets/Act-D0HWqtn0.js +0 -1
  273. package/static/app/assets/AdminConsole-D-QDW-A4.js +0 -1
  274. package/static/app/assets/BrainHome-Btns-_TA.js +0 -2
  275. package/static/app/assets/BrainSignals-2dHQNkns.js +0 -1
  276. package/static/app/assets/Capture-CT8v1StE.js +0 -1
  277. package/static/app/assets/CommandPalette-DoLXC2KH.js +0 -1
  278. package/static/app/assets/Library-DDoxFE5c.js +0 -1
  279. package/static/app/assets/ProductFlow-DOYf7JIs.js +0 -1
  280. package/static/app/assets/ReviewCard-COQsqidK.js +0 -3
  281. package/static/app/assets/System-BRllvYXd.js +0 -1
  282. package/static/app/assets/arrow-left-DnyMzss-.js +0 -1
  283. package/static/app/assets/brain-uMb_5hnO.js +0 -1
  284. package/static/app/assets/index-0AvoEBzJ.js +0 -10
  285. package/static/app/assets/input-B_5ZJ9oy.js +0 -1
  286. package/static/app/assets/primitives-CVwew78r.js +0 -1
  287. package/static/app/assets/search-DkhnOKZt.js +0 -1
  288. package/static/app/assets/utils-DcDMoZIe.js +0 -4
  289. package/static/app/assets/workspace-LtRRSKTf.js +0 -1
@@ -76,6 +76,14 @@ class AppContext:
76
76
  workspace_graph: Optional[Callable[[], Any]] = None
77
77
  graph_stats: Optional[Callable[[], dict]] = None
78
78
 
79
+ # ── review center ─────────────────────────────────────────────────────
80
+ # ``ReviewQueueService``, reached through a provider like
81
+ # ``workspace_graph`` above: the queue is built in a later runtime phase
82
+ # than this context, so a value captured here would be ``None`` forever.
83
+ # Call it per request; ``None`` means no Review Center is wired (tests,
84
+ # headless helpers) and the callers stage nothing rather than writing.
85
+ review_queue: Optional[Callable[[], Any]] = None
86
+
79
87
  # ── workspace payload providers / skills ──────────────────────────────
80
88
  workspace_models: Optional[Callable[[], dict]] = None
81
89
  workspace_settings: Optional[Callable[[], dict]] = None
@@ -16,7 +16,7 @@ from typing import Any, Dict, List
16
16
 
17
17
  from latticeai.core.legacy_compatibility import legacy_shim_report
18
18
 
19
- ARCHITECTURE_VERSION_TARGET = "11.1.0"
19
+ ARCHITECTURE_VERSION_TARGET = "11.4.0"
20
20
 
21
21
  PREFERRED_REFACTORING_ORDER = [
22
22
  "agent-runtime",
@@ -53,6 +53,19 @@ def _symbol_exists(dotted: str) -> bool:
53
53
  return False
54
54
 
55
55
 
56
+ def _package_sources(root: Path, relative_package: str) -> List[str]:
57
+ """Every ``*.py`` in a package, as repo-relative paths.
58
+
59
+ Returns the missing marker path when the package is absent, so a gate that
60
+ scans a package fails the same way it would for a deleted single file.
61
+ """
62
+ directory = root / relative_package
63
+ sources = sorted(directory.glob("*.py")) if directory.is_dir() else []
64
+ if not sources:
65
+ return [f"{relative_package}/__init__.py"]
66
+ return [str(path.relative_to(root)) for path in sources]
67
+
68
+
56
69
  def _forbidden_patterns(root: Path, relative_path: str, patterns: List[str]) -> List[str]:
57
70
  path = root / relative_path
58
71
  try:
@@ -71,18 +84,26 @@ def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
71
84
  "latticeai/app_factory.py",
72
85
  ["build_runtime_namespace(locals", "dict(locals())"],
73
86
  )
74
- model_runtime_forbidden = _forbidden_patterns(
75
- root,
76
- "latticeai/services/model_runtime.py",
77
- [
78
- "def _sync_globals",
79
- "global router",
80
- "STATE = ModelRuntimeState",
81
- "_STATE_EXPORTS",
82
- "def __getattr__(name:",
83
- "from fastapi import HTTPException",
84
- ],
85
- )
87
+ # v11.3.0: model_runtime.py became a package. The ambient-state patterns
88
+ # below could reappear in any of its submodules, so every file in the
89
+ # package is scanned — checking only __init__.py would leave the gate
90
+ # blind one directory down.
91
+ model_runtime_forbidden = [
92
+ finding
93
+ for relative_path in _package_sources(root, "latticeai/services/model_runtime")
94
+ for finding in _forbidden_patterns(
95
+ root,
96
+ relative_path,
97
+ [
98
+ "def _sync_globals",
99
+ "global router",
100
+ "STATE = ModelRuntimeState",
101
+ "_STATE_EXPORTS",
102
+ "def __getattr__(name:",
103
+ "from fastapi import HTTPException",
104
+ ],
105
+ )
106
+ ]
86
107
  model_runtime_forbidden.extend(
87
108
  _forbidden_patterns(
88
109
  root,
@@ -97,11 +118,19 @@ def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
97
118
  ["from fastapi import HTTPException", "raise HTTPException"],
98
119
  )
99
120
  )
100
- agent_alias_forbidden = _forbidden_patterns(
101
- root,
102
- "latticeai/core/agent.py",
103
- ["AgentRuntime = SingleAgentRuntime"],
104
- )
121
+ # v11.3.0: the single-agent loop is a package. The compatibility alias
122
+ # could reappear in any of its submodules, so every file in the package is
123
+ # scanned — checking only __init__.py would leave the gate blind one
124
+ # directory down.
125
+ agent_alias_forbidden = [
126
+ finding
127
+ for relative_path in _package_sources(root, "latticeai/core/agent")
128
+ for finding in _forbidden_patterns(
129
+ root,
130
+ relative_path,
131
+ ["AgentRuntime = SingleAgentRuntime"],
132
+ )
133
+ ]
105
134
 
106
135
  gates = [
107
136
  ArchitectureGate(
@@ -180,7 +209,7 @@ def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
180
209
  evidence=[
181
210
  "frontend/src/components/ProductFlow.tsx Wake Brain entry",
182
211
  "frontend/src/features/brain/BrainHome.tsx memory rings",
183
- "tests/visual/v3.spec.js first-run and Brain depth coverage",
212
+ "tests/visual/v3.home.spec.js first-run and Brain depth coverage",
184
213
  ],
185
214
  ),
186
215
  ArchitectureGate(
@@ -55,6 +55,13 @@ _SIGNATURE_SIMILARITY = 0.6
55
55
  _MIN_SUGGESTION_CONFIDENCE = 0.35
56
56
  _LOW_CONFIDENCE_THRESHOLD = 0.5
57
57
  _KG_GROUNDING_LIMIT = 5
58
+ #: Charged when the graph *was* consulted and found nothing related. v11.2.0
59
+ #: fixes a dead policy: the floor above was unreachable for recurring questions
60
+ #: (the cheapest possible question — asked twice, one phrasing, no recipe —
61
+ #: scored 0.475), so the suppression branch could only ever be exercised by a
62
+ #: test that moved the constant. Absence of grounding is real evidence, and
63
+ #: charging for it is what makes the floor mean something again.
64
+ _UNGROUNDED_PENALTY = 0.15
58
65
 
59
66
 
60
67
  def _question_confidence(
@@ -67,7 +74,13 @@ def _question_confidence(
67
74
 
68
75
  Evidence factors: how often the question repeats, how many distinct
69
76
  phrasings exist, whether it maps onto a known starter-recipe intent, and
70
- (when the graph is available) how many Brain nodes relate to it.
77
+ what the Brain knows about it.
78
+
79
+ That last factor is three-valued on purpose. ``None`` means the graph could
80
+ not be consulted — unknown, so nothing is added or taken away. A positive
81
+ count is supporting evidence. **Zero** is not the same as unknown: the
82
+ graph was asked, and it knows nothing about this question, which is a
83
+ reason to be less sure rather than a reason to be neutral.
71
84
  """
72
85
  score = 0.3 + 0.5 * min(1.0, (int(count) - 1) / 4)
73
86
  score += min(0.15, 0.05 * len(examples or []))
@@ -75,13 +88,16 @@ def _question_confidence(
75
88
  score += 0.15
76
89
  if kg_related:
77
90
  score += min(0.2, 0.05 * int(kg_related))
91
+ elif kg_related == 0:
92
+ score -= _UNGROUNDED_PENALTY
78
93
  factors = {
79
94
  "repeat_count": int(count),
80
95
  "distinct_examples": len(examples or []),
81
96
  "intent_match": bool(recipe_id),
82
97
  "kg_related_nodes": kg_related,
98
+ "kg_grounded": None if kg_related is None else bool(kg_related),
83
99
  }
84
- return round(min(1.0, score), 2), factors
100
+ return round(max(0.0, min(1.0, score)), 2), factors
85
101
 
86
102
 
87
103
  def _source_confidence(indexed: int, watch_enabled: bool) -> tuple:
@@ -357,6 +373,10 @@ class AutomationIntelligenceService:
357
373
  pattern["count"], pattern["examples"], recipe_id, kg_related
358
374
  )
359
375
  if confidence < _MIN_SUGGESTION_CONFIDENCE:
376
+ # Asked a couple of times, phrased one way, matching no recipe,
377
+ # and the Brain knows nothing about it — not enough to propose
378
+ # automating. Counted, so the surface can say how many were held
379
+ # back rather than silently showing fewer.
360
380
  suppressed_low_confidence += 1
361
381
  continue
362
382
  if recipe_id:
@@ -0,0 +1,58 @@
1
+ """Proactive Brain Intelligence service (v9.3.0).
2
+
3
+ The Brain graduates from a passive store to an active steward of its own
4
+ knowledge. This service wires the previously dormant quality layer
5
+ (:mod:`lattice_brain.quality` — dedupe, merge, conflict and temporal
6
+ contradiction detection, retention) into router-facing capabilities:
7
+
8
+ * **health_report** — scored diagnosis of the Brain across freshness,
9
+ connectivity, embedding coverage, and contradiction pressure, with
10
+ recommended next actions. Every number is read from the live stores;
11
+ a missing store degrades the dimension to ``unavailable``, never a guess.
12
+ * **insights** — a proactive digest: recent knowledge growth, most active
13
+ types, stale knowledge, orphan (disconnected) nodes, and suggested
14
+ questions grounded in real node titles.
15
+ * **contradictions** — surfaced conflicts across workspace memories
16
+ (negation/preference conflicts, temporal contradictions) plus explicit
17
+ CONTRADICTS edges already recorded in the graph.
18
+ * **consolidate** — duplicate-memory and duplicate-edge detection. Dry-run
19
+ by default (consent-first, like every Brain automation); ``apply=True``
20
+ prunes only exact duplicate workspace memories through the audited
21
+ MemoryService path and never touches graph content.
22
+
23
+ Pure service: no FastAPI, no globals. Collaborators are injected.
24
+
25
+ Split into cohesive submodules in v11.3.0 (no behaviour change): ``constants``
26
+ (windows + the two shared readings), ``proposals`` (the review-queue path and
27
+ the proactive brief), ``sampling`` (the shared graph slice and memory read),
28
+ ``health`` (the scored diagnosis and vector freshness), ``digest`` (insights,
29
+ garden, graph-layer quality reads), ``consistency`` (contradiction and
30
+ consolidation scans), and ``service`` (the composed class). This module
31
+ re-exports every name the single file exposed, so
32
+ ``latticeai.services.brain_intelligence.X`` keeps working.
33
+
34
+ Stubbing note: rebinding one of these *here* changes only this module's name.
35
+ The submodule that calls it holds its own reference, so a test standing in for
36
+ ``LOGGER`` or ``_parse_ts`` patches the submodule that uses it.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ from lattice_brain.quality import GraphEdgeQualityManager as GraphEdgeQualityManager
42
+ from lattice_brain.quality import MemoryQualityManager as MemoryQualityManager
43
+
44
+ # noqa on the next line, not a redundant alias: the single file exposed this
45
+ # name *renamed* (``brain_intelligence._now``), and reproducing that surface
46
+ # exactly is the point — importing it as ``now_iso`` would add a name the
47
+ # module never had.
48
+ from latticeai.core.timeutil import now_iso as _now # noqa: F401
49
+
50
+ from .constants import _GRAPH_SAMPLE_LIMIT as _GRAPH_SAMPLE_LIMIT
51
+ from .constants import _RECENT_DAYS as _RECENT_DAYS
52
+ from .constants import _STALE_DAYS as _STALE_DAYS
53
+ from .constants import LOGGER as LOGGER
54
+ from .constants import _no_graph_reason as _no_graph_reason
55
+ from .constants import _parse_ts as _parse_ts
56
+ from .service import BrainIntelligenceService as BrainIntelligenceService
57
+
58
+ __all__ = ["BrainIntelligenceService"]
@@ -0,0 +1,71 @@
1
+ """The seam the Brain Intelligence mixins share.
2
+
3
+ ``BrainIntelligenceService`` is assembled from the proposal, sampling, health,
4
+ digest and consistency mixins. Each reads constructor state it does not own,
5
+ and several call methods another mixin implements — ``_graph_sample`` feeds
6
+ almost everything, ``_proactive`` gates the graph-layer surfaces, and the
7
+ garden view composes the contradiction scan. That contract existed as an
8
+ unwritten convention inside one class; this module writes it down.
9
+
10
+ Typing-only, exactly like :mod:`lattice_brain.portability._contract`: every
11
+ body below is a bare ``raise NotImplementedError`` that the composed class
12
+ always overrides, so the MRO and every method resolution stay what the
13
+ single-file class had. Adding a cross-mixin call without declaring it here is a
14
+ type error.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Any, Dict, List, Optional
20
+
21
+
22
+ class BrainIntelligenceCore:
23
+ """What any Brain Intelligence mixin may assume about ``self``.
24
+
25
+ Members are declared, not implemented: the implementation lives in
26
+ ``service.py`` (constructor state) or in whichever mixin owns the method.
27
+ """
28
+
29
+ # ── State owned by BrainIntelligenceService.__init__ ─────────────────────
30
+ _kg: Any
31
+ _memory: Any
32
+ _enable_graph: bool
33
+ _memory_quality: Any
34
+ _edge_quality: Any
35
+ _proactive_brain: Any
36
+ _review_queue_service: Any
37
+ _synthesizer: Any
38
+
39
+ # ── proposals.py: the review-queue seam every write door goes through ────
40
+ def _review_queue(self) -> Any:
41
+ raise NotImplementedError
42
+
43
+ def _synthesis(self) -> Any:
44
+ raise NotImplementedError
45
+
46
+ @staticmethod
47
+ def _no_queue(detail: str) -> Dict[str, Any]:
48
+ raise NotImplementedError
49
+
50
+ def importance_report(
51
+ self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
52
+ ) -> Dict[str, Any]:
53
+ raise NotImplementedError
54
+
55
+ # ── sampling.py: the graph slice and the memory tier ─────────────────────
56
+ def _proactive(self) -> Any:
57
+ raise NotImplementedError
58
+
59
+ def _graph_sample(self, *, workspace_id: Optional[str]) -> Dict[str, Any]:
60
+ raise NotImplementedError
61
+
62
+ def _workspace_memories(
63
+ self, *, user_email: Optional[str], workspace_id: Optional[str]
64
+ ) -> List[Dict[str, Any]]:
65
+ raise NotImplementedError
66
+
67
+ # ── consistency.py: what the garden view composes ────────────────────────
68
+ def contradictions(
69
+ self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
70
+ ) -> Dict[str, Any]:
71
+ raise NotImplementedError
@@ -0,0 +1,193 @@
1
+ """What the Brain disagrees with itself about, and what it says twice.
2
+
3
+ Two scans that wire the previously dormant quality layer
4
+ (:mod:`lattice_brain.quality`) to real stores:
5
+
6
+ * :meth:`contradictions` — four independent signals in one list: negation and
7
+ preference conflicts between workspace memories, temporal contradictions,
8
+ explicit ``CONTRADICTS`` edges already recorded in the graph, and the graph
9
+ layer's own node-content detector. Each item carries the signal that found
10
+ it, so a reviewer can weigh them differently.
11
+ * :meth:`consolidate` — duplicate memories and duplicate edges. Dry-run by
12
+ default, consent-first like every Brain automation. ``apply=True`` prunes
13
+ only exact duplicate *workspace memories*, through the audited MemoryService
14
+ path; graph content is never mutated here — the node merge plan is surfaced
15
+ for a governed apply path to adopt.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ from latticeai.core.timeutil import now_iso as _now
23
+
24
+ from ._contract import BrainIntelligenceCore as _Core
25
+ from .constants import LOGGER
26
+
27
+
28
+ class BrainConsistencyMixin(_Core):
29
+ """Contradiction and consolidation scans. Mixed into the service."""
30
+
31
+ # ── contradictions ───────────────────────────────────────────────────
32
+
33
+ def contradictions(
34
+ self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
35
+ ) -> Dict[str, Any]:
36
+ memories = self._workspace_memories(user_email=user_email, workspace_id=workspace_id)
37
+ memory_rows = [
38
+ {
39
+ "id": str(m.get("id") or f"mem-{i}"),
40
+ "content": str(m.get("content") or ""),
41
+ "score": 0.6,
42
+ "source": "workspace",
43
+ "timestamp": m.get("created_at") or m.get("timestamp") or 0,
44
+ }
45
+ for i, m in enumerate(memories)
46
+ if str(m.get("content") or "").strip()
47
+ ]
48
+
49
+ conflicts: List[Dict[str, Any]] = []
50
+ candidates = self._memory_quality.extract_candidates(memory_rows)
51
+ for candidate in self._memory_quality.detect_conflicts(candidates):
52
+ pair_conflicts = [c for c in candidate.conflicts if c.startswith("conflict:contradicts:")]
53
+ for marker in pair_conflicts:
54
+ other_id = marker.rsplit(":", 1)[-1]
55
+ if any(
56
+ c["kind"] == "memory_pair"
57
+ and {c["left_id"], c["right_id"]} == {candidate.id, other_id}
58
+ for c in conflicts
59
+ ):
60
+ continue
61
+ other: Dict[str, Any] | None = next(
62
+ (r for r in memory_rows if r["id"] == other_id), None
63
+ )
64
+ conflicts.append({
65
+ "kind": "memory_pair",
66
+ "left_id": candidate.id,
67
+ "left_content": candidate.content[:200],
68
+ "right_id": other_id,
69
+ "right_content": (other or {}).get("content", "")[:200],
70
+ "signal": "preference_negation",
71
+ })
72
+
73
+ temporal = self._memory_quality.detect_temporal_contradictions(memory_rows)
74
+ temporal_items = [
75
+ {
76
+ "kind": "temporal",
77
+ "id": item.get("id"),
78
+ "content": str(item.get("content") or "")[:200],
79
+ "signal": item.get("proactive_flag"),
80
+ }
81
+ for item in temporal
82
+ ]
83
+
84
+ edge_items: List[Dict[str, Any]] = []
85
+ sample = self._graph_sample(workspace_id=workspace_id)
86
+ for edge in sample["edges"]:
87
+ if "CONTRADICT" in str(edge.get("type") or "").upper():
88
+ edge_items.append({
89
+ "kind": "graph_edge",
90
+ "id": edge.get("id"),
91
+ "source": edge.get("source") or edge.get("from_node"),
92
+ "target": edge.get("target") or edge.get("to_node"),
93
+ "signal": "contradicts_edge",
94
+ })
95
+
96
+ # v9.6.x additive: graph-layer node-content contradictions (proactive
97
+ # detector over node title/summary), on top of the memory + edge scans.
98
+ graph_pair_items: List[Dict[str, Any]] = []
99
+ proactive = self._proactive()
100
+ if proactive is not None:
101
+ try:
102
+ graph_result = proactive.detect_contradictions(workspace_id=workspace_id)
103
+ graph_pair_items = [
104
+ {"kind": "graph_node_pair", **pair}
105
+ for pair in graph_result.get("node_pairs") or []
106
+ ]
107
+ except Exception:
108
+ LOGGER.exception("graph contradiction scan failed")
109
+
110
+ items = conflicts + temporal_items + edge_items + graph_pair_items
111
+ return {
112
+ "items": items,
113
+ "count": len(items),
114
+ "sources": {
115
+ "memory_pairs": len(conflicts),
116
+ "temporal": len(temporal_items),
117
+ "graph_edges": len(edge_items),
118
+ "graph_node_pairs": len(graph_pair_items),
119
+ },
120
+ "memories_scanned": len(memory_rows),
121
+ "generated_at": _now(),
122
+ }
123
+
124
+ # ── consolidation ────────────────────────────────────────────────────
125
+
126
+ def consolidate(
127
+ self,
128
+ *,
129
+ apply: bool = False,
130
+ user_email: Optional[str] = None,
131
+ workspace_id: Optional[str] = None,
132
+ ) -> Dict[str, Any]:
133
+ memories = self._workspace_memories(user_email=user_email, workspace_id=workspace_id)
134
+ memory_rows = [
135
+ {
136
+ "id": str(m.get("id") or f"mem-{i}"),
137
+ "content": str(m.get("content") or ""),
138
+ "score": 0.6,
139
+ "source": "workspace",
140
+ }
141
+ for i, m in enumerate(memories)
142
+ if str(m.get("content") or "").strip()
143
+ ]
144
+ candidates = self._memory_quality.extract_candidates(memory_rows)
145
+ kept = self._memory_quality.dedupe(candidates)
146
+ kept_ids = {c.id for c in kept}
147
+ duplicate_memory_ids = [c.id for c in candidates if c.id not in kept_ids]
148
+
149
+ sample = self._graph_sample(workspace_id=workspace_id)
150
+ duplicate_edge_ids = [
151
+ edge_id
152
+ for edge_id in self._edge_quality.detect_duplicate_edges(sample["edges"])
153
+ if edge_id
154
+ ]
155
+
156
+ pruned = 0
157
+ if apply and duplicate_memory_ids and self._memory is not None:
158
+ try:
159
+ result = self._memory.prune(
160
+ ids=duplicate_memory_ids,
161
+ user_email=user_email,
162
+ workspace_id=workspace_id,
163
+ )
164
+ pruned = int(result.get("count") or 0)
165
+ except Exception:
166
+ LOGGER.exception("consolidation prune failed")
167
+
168
+ # v9.6.x additive: graph-layer node merge plan. Always dry-run from
169
+ # this service — graph content changes stay proposal-first; the plan
170
+ # is surfaced so a governed apply path can adopt it later.
171
+ graph_consolidation: Optional[Dict[str, Any]] = None
172
+ proactive = self._proactive()
173
+ if proactive is not None:
174
+ try:
175
+ graph_consolidation = proactive.consolidate_duplicates(
176
+ workspace_id=workspace_id, dry_run=True
177
+ )
178
+ except Exception:
179
+ LOGGER.exception("graph consolidation plan failed")
180
+
181
+ return {
182
+ "mode": "applied" if apply else "dry_run",
183
+ "memories_scanned": len(memory_rows),
184
+ "duplicate_memories": duplicate_memory_ids,
185
+ "duplicate_memory_count": len(duplicate_memory_ids),
186
+ "pruned": pruned,
187
+ # Graph edges are reported for review only; consolidation never
188
+ # mutates graph content directly.
189
+ "duplicate_edges": duplicate_edge_ids[:50],
190
+ "duplicate_edge_count": len(duplicate_edge_ids),
191
+ "graph_consolidation": graph_consolidation,
192
+ "generated_at": _now(),
193
+ }
@@ -0,0 +1,47 @@
1
+ """Windows, sample sizes, and the two readings every surface shares.
2
+
3
+ Leaf module: literals and pure functions only, no service and no store. The
4
+ health report, the insights digest, the garden and the contradiction scan all
5
+ measure against the same windows, which is what makes their numbers
6
+ comparable.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from datetime import datetime, timezone
13
+ from typing import Any, Optional
14
+
15
+ LOGGER = logging.getLogger(__name__)
16
+
17
+ _STALE_DAYS = 45
18
+ _RECENT_DAYS = 7
19
+ _GRAPH_SAMPLE_LIMIT = 800
20
+
21
+
22
+ def _parse_ts(value: Any) -> Optional[datetime]:
23
+ text = str(value or "").strip()
24
+ if not text:
25
+ return None
26
+ try:
27
+ parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
28
+ except ValueError:
29
+ return None
30
+ if parsed.tzinfo is None:
31
+ parsed = parsed.replace(tzinfo=timezone.utc)
32
+ return parsed
33
+
34
+
35
+ def _no_graph_reason(graph_available: bool) -> str:
36
+ """Why a graph-derived health dimension has nothing to say.
37
+
38
+ Two different situations that both end in ``status: "unavailable"``: the
39
+ graph could not be read at all, and the graph read fine but holds nothing
40
+ yet. Telling them apart is the difference between "something is broken"
41
+ and "you have not saved anything yet".
42
+ """
43
+ return (
44
+ "no knowledge saved yet"
45
+ if graph_available
46
+ else "the knowledge graph could not be read"
47
+ )