ltcai 4.3.3 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +53 -20
  2. package/docs/CHANGELOG.md +122 -0
  3. package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
  4. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +49 -0
  5. package/docs/V4_5_0_GRAPH_UX_REPORT.md +34 -0
  6. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +40 -0
  7. package/docs/V4_5_0_ONBOARDING_REPORT.md +31 -0
  8. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +49 -0
  9. package/docs/V4_5_0_VALIDATION_REPORT.md +60 -0
  10. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +33 -0
  11. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +37 -0
  12. package/docs/V4_5_1_NAVIGATION_REPORT.md +37 -0
  13. package/docs/V4_5_1_ONBOARDING_REPORT.md +29 -0
  14. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +61 -0
  15. package/docs/V4_5_1_RC_ARTIFACTS.md +44 -0
  16. package/docs/V4_5_1_UX_REPORT.md +45 -0
  17. package/docs/V4_5_1_VALIDATION_REPORT.md +54 -0
  18. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +30 -0
  19. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +16 -16
  20. package/docs/architecture.md +8 -4
  21. package/frontend/src/App.tsx +152 -91
  22. package/frontend/src/api/client.ts +83 -1
  23. package/frontend/src/components/FirstRunGuide.tsx +99 -0
  24. package/frontend/src/components/primitives.tsx +131 -25
  25. package/frontend/src/components/ui/badge.tsx +2 -2
  26. package/frontend/src/components/ui/button.tsx +7 -7
  27. package/frontend/src/components/ui/card.tsx +5 -5
  28. package/frontend/src/components/ui/input.tsx +1 -1
  29. package/frontend/src/components/ui/textarea.tsx +1 -1
  30. package/frontend/src/pages/Act.tsx +58 -28
  31. package/frontend/src/pages/Ask.tsx +51 -19
  32. package/frontend/src/pages/Brain.tsx +60 -42
  33. package/frontend/src/pages/Capture.tsx +24 -24
  34. package/frontend/src/pages/Library.tsx +222 -32
  35. package/frontend/src/pages/System.tsx +56 -34
  36. package/frontend/src/routes.ts +15 -13
  37. package/frontend/src/store/appStore.ts +8 -1
  38. package/frontend/src/styles.css +666 -36
  39. package/lattice_brain/__init__.py +38 -23
  40. package/lattice_brain/_kg_common.py +11 -1
  41. package/lattice_brain/context.py +212 -2
  42. package/lattice_brain/conversations.py +234 -1
  43. package/lattice_brain/discovery.py +11 -1
  44. package/lattice_brain/documents.py +11 -1
  45. package/lattice_brain/graph/__init__.py +28 -0
  46. package/lattice_brain/graph/_kg_common.py +1123 -0
  47. package/lattice_brain/graph/curator.py +473 -0
  48. package/lattice_brain/graph/discovery.py +1455 -0
  49. package/lattice_brain/graph/documents.py +218 -0
  50. package/lattice_brain/graph/identity.py +175 -0
  51. package/lattice_brain/graph/ingest.py +644 -0
  52. package/lattice_brain/graph/network.py +205 -0
  53. package/lattice_brain/graph/projection.py +571 -0
  54. package/lattice_brain/graph/provenance.py +401 -0
  55. package/lattice_brain/graph/retrieval.py +1341 -0
  56. package/lattice_brain/graph/schema.py +640 -0
  57. package/lattice_brain/graph/store.py +237 -0
  58. package/lattice_brain/graph/write_master.py +225 -0
  59. package/lattice_brain/identity.py +11 -13
  60. package/lattice_brain/ingest.py +11 -1
  61. package/lattice_brain/ingestion.py +318 -0
  62. package/lattice_brain/memory.py +100 -1
  63. package/lattice_brain/network.py +11 -1
  64. package/lattice_brain/portability.py +431 -0
  65. package/lattice_brain/projection.py +11 -1
  66. package/lattice_brain/provenance.py +11 -1
  67. package/lattice_brain/retrieval.py +11 -1
  68. package/lattice_brain/runtime/__init__.py +32 -0
  69. package/lattice_brain/runtime/agent_runtime.py +569 -0
  70. package/lattice_brain/runtime/hooks.py +754 -0
  71. package/lattice_brain/runtime/multi_agent.py +795 -0
  72. package/lattice_brain/schema.py +11 -1
  73. package/lattice_brain/store.py +10 -2
  74. package/lattice_brain/workflow.py +461 -0
  75. package/lattice_brain/write_master.py +11 -1
  76. package/latticeai/__init__.py +1 -1
  77. package/latticeai/api/agents.py +2 -2
  78. package/latticeai/api/browser.py +1 -1
  79. package/latticeai/api/chat.py +1 -1
  80. package/latticeai/api/computer_use.py +1 -1
  81. package/latticeai/api/hooks.py +2 -2
  82. package/latticeai/api/mcp.py +1 -1
  83. package/latticeai/api/models.py +107 -18
  84. package/latticeai/api/tools.py +1 -1
  85. package/latticeai/api/workflow_designer.py +2 -2
  86. package/latticeai/app_factory.py +4 -4
  87. package/latticeai/brain/__init__.py +24 -6
  88. package/latticeai/brain/_kg_common.py +11 -1117
  89. package/latticeai/brain/context.py +12 -208
  90. package/latticeai/brain/conversations.py +12 -231
  91. package/latticeai/brain/discovery.py +13 -1451
  92. package/latticeai/brain/documents.py +13 -214
  93. package/latticeai/brain/identity.py +11 -169
  94. package/latticeai/brain/ingest.py +13 -640
  95. package/latticeai/brain/memory.py +12 -97
  96. package/latticeai/brain/network.py +12 -200
  97. package/latticeai/brain/projection.py +13 -567
  98. package/latticeai/brain/provenance.py +13 -397
  99. package/latticeai/brain/retrieval.py +13 -1337
  100. package/latticeai/brain/schema.py +12 -635
  101. package/latticeai/brain/store.py +13 -233
  102. package/latticeai/brain/write_master.py +13 -221
  103. package/latticeai/core/agent.py +1 -1
  104. package/latticeai/core/agent_registry.py +2 -2
  105. package/latticeai/core/builtin_hooks.py +2 -2
  106. package/latticeai/core/graph_curator.py +6 -468
  107. package/latticeai/core/hooks.py +6 -749
  108. package/latticeai/core/marketplace.py +1 -1
  109. package/latticeai/core/model_compat.py +250 -0
  110. package/latticeai/core/multi_agent.py +6 -790
  111. package/latticeai/core/workflow_engine.py +6 -456
  112. package/latticeai/core/workspace_os.py +1 -1
  113. package/latticeai/models/router.py +136 -32
  114. package/latticeai/services/agent_runtime.py +6 -564
  115. package/latticeai/services/ingestion.py +6 -313
  116. package/latticeai/services/kg_portability.py +6 -426
  117. package/latticeai/services/model_catalog.py +2 -2
  118. package/latticeai/services/model_recommendation.py +8 -1
  119. package/latticeai/services/model_runtime.py +18 -3
  120. package/latticeai/services/platform_runtime.py +3 -3
  121. package/latticeai/services/run_executor.py +1 -1
  122. package/latticeai/services/upload_service.py +1 -1
  123. package/p_reinforce.py +1 -1
  124. package/package.json +1 -1
  125. package/scripts/build_frontend_assets.mjs +12 -1
  126. package/scripts/bump_version.py +1 -1
  127. package/scripts/wheel_smoke.py +7 -0
  128. package/src-tauri/Cargo.lock +1 -1
  129. package/src-tauri/Cargo.toml +1 -1
  130. package/src-tauri/tauri.conf.json +1 -1
  131. package/static/app/asset-manifest.json +5 -5
  132. package/static/app/assets/index-3G8qcrIS.js +336 -0
  133. package/static/app/assets/index-3G8qcrIS.js.map +1 -0
  134. package/static/app/assets/index-C0wYZp7k.css +2 -0
  135. package/static/app/index.html +2 -2
  136. package/static/app/assets/index-CHHal8Zl.css +0 -2
  137. package/static/app/assets/index-pdzil9ac.js +0 -333
  138. package/static/app/assets/index-pdzil9ac.js.map +0 -1
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "4.3.3"
14
+ MARKETPLACE_VERSION = "4.5.1"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -12,11 +12,14 @@
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
+ import importlib.util
16
+ import json
15
17
  import logging
16
18
  import re
17
19
  import threading
18
20
  import time
19
21
  from dataclasses import dataclass, asdict
22
+ from pathlib import Path
20
23
  from typing import Any, Dict, List, Optional, Tuple
21
24
 
22
25
  logger = logging.getLogger(__name__)
@@ -119,6 +122,251 @@ def get_model_profile(model_id: str, engine: Optional[str] = None) -> Dict[str,
119
122
  return base
120
123
 
121
124
 
125
+ # ── Runtime compatibility checks ─────────────────────────────────────────────
126
+
127
+ GEMMA4_MLX_UNIFIED_MODULE = "mlx_vlm.models.gemma4_unified"
128
+ GEMMA4_MLX_LM_MODULES = ("mlx_lm.models.gemma4", "mlx_lm.models.gemma4_text")
129
+ GEMMA4_UNIFIED_ID_PATTERN = re.compile(r"gemma[-_/ ]?4[-_/ ]?12b", re.I)
130
+
131
+
132
+ def _module_available(module: str) -> bool:
133
+ try:
134
+ return importlib.util.find_spec(module) is not None
135
+ except (ImportError, ModuleNotFoundError, AttributeError, ValueError):
136
+ return False
137
+
138
+
139
+ def _is_gemma4(model_id: str) -> bool:
140
+ raw = str(model_id or "").lower()
141
+ return bool(re.search(r"gemma[-_/ ]?4", raw))
142
+
143
+
144
+ def _hf_model_dir(repo_id: str) -> Path:
145
+ return Path.home() / ".ltcai" / "hf-models" / repo_id.replace("/", "__")
146
+
147
+
148
+ def _local_model_type(model_id: str) -> Optional[str]:
149
+ raw = str(model_id or "").strip()
150
+ if "gemma4_unified" in raw.lower():
151
+ return "gemma4_unified"
152
+ candidates = []
153
+ explicit = Path(raw).expanduser()
154
+ if raw and explicit.exists():
155
+ candidates.append(explicit / "config.json")
156
+ candidates.append(_hf_model_dir(raw) / "config.json")
157
+ for config_path in candidates:
158
+ try:
159
+ if config_path.exists():
160
+ data = json.loads(config_path.read_text(encoding="utf-8"))
161
+ model_type = str(data.get("model_type") or "").strip().lower()
162
+ if model_type:
163
+ return model_type
164
+ except Exception:
165
+ logger.debug("failed to read model config %s", config_path, exc_info=True)
166
+ if GEMMA4_UNIFIED_ID_PATTERN.search(raw):
167
+ return "gemma4_unified"
168
+ return None
169
+
170
+
171
+ def _gemma4_runtime_candidates(raw_model_id: str) -> List[Dict[str, Any]]:
172
+ mlx_available = _module_available("mlx")
173
+ mlx_vlm_available = mlx_available and _module_available("mlx_vlm")
174
+ mlx_vlm_unified_available = mlx_vlm_available and _module_available(GEMMA4_MLX_UNIFIED_MODULE)
175
+ mlx_lm_available = mlx_available and _module_available("mlx_lm")
176
+ mlx_lm_gemma4_available = mlx_lm_available and any(_module_available(module) for module in GEMMA4_MLX_LM_MODULES)
177
+ return [
178
+ {
179
+ "engine": "local_mlx",
180
+ "runtime": "MLX-VLM",
181
+ "load_id": raw_model_id,
182
+ "available": mlx_vlm_available,
183
+ "supports_gemma4_unified": mlx_vlm_unified_available,
184
+ "role": "v3_primary",
185
+ },
186
+ {
187
+ "engine": "local_mlx",
188
+ "runtime": "MLX-LM",
189
+ "load_id": raw_model_id,
190
+ "available": mlx_lm_gemma4_available,
191
+ "role": "v3_text_fallback",
192
+ },
193
+ {
194
+ "engine": "ollama",
195
+ "runtime": "Ollama GGUF",
196
+ "load_id": "ollama:hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M",
197
+ "available": None,
198
+ "role": "gguf_local_server",
199
+ },
200
+ {
201
+ "engine": "lmstudio",
202
+ "runtime": "LM Studio GGUF",
203
+ "load_id": "lmstudio:ggml-org/gemma-4-12B-it-GGUF",
204
+ "available": None,
205
+ "role": "gguf_local_server",
206
+ },
207
+ {
208
+ "engine": "llamacpp",
209
+ "runtime": "llama.cpp GGUF",
210
+ "load_id": "llamacpp:ggml-org/gemma-4-12B-it-GGUF",
211
+ "available": None,
212
+ "role": "gguf_local_server",
213
+ },
214
+ ]
215
+
216
+
217
+ def model_runtime_compatibility(model_id: str, engine: Optional[str] = None) -> Dict[str, Any]:
218
+ """Return a lightweight pre-load runtime compatibility signal.
219
+
220
+ This intentionally checks only known fast failure modes. The loader and
221
+ smoke test remain the final authority, but the UI must not present a model
222
+ as ready when the installed runtime is known to lack its required loader.
223
+ """
224
+ normalized_engine = (engine or "").strip().lower()
225
+ if normalized_engine in {"", "mlx"}:
226
+ normalized_engine = "local_mlx"
227
+ raw_model_id = str(model_id or "")
228
+ if raw_model_id.startswith(("local_mlx:", "mlx:")):
229
+ raw_model_id = raw_model_id.split(":", 1)[1]
230
+
231
+ payload: Dict[str, Any] = {
232
+ "model_id": raw_model_id,
233
+ "engine": normalized_engine or None,
234
+ "family": detect_model_family(raw_model_id),
235
+ "status": "supported",
236
+ "supported": True,
237
+ "checked": True,
238
+ "runtime": None,
239
+ "preferred_runtime": None,
240
+ "runtime_candidates": [],
241
+ "missing_components": [],
242
+ "user_message": None,
243
+ "recovery_guidance": [],
244
+ "alternatives": [],
245
+ }
246
+
247
+ if normalized_engine != "local_mlx" or not _is_gemma4(raw_model_id):
248
+ return payload
249
+
250
+ candidates = _gemma4_runtime_candidates(raw_model_id)
251
+ payload["runtime_candidates"] = candidates
252
+ payload["runtime"] = "MLX-VLM"
253
+ payload["preferred_runtime"] = "MLX-VLM"
254
+ model_type = _local_model_type(raw_model_id)
255
+ if model_type:
256
+ payload["model_type"] = model_type
257
+
258
+ mlx_available = _module_available("mlx")
259
+ mlx_vlm_available = _module_available("mlx_vlm")
260
+ mlx_lm_available = any(bool(candidate.get("available")) for candidate in candidates if candidate.get("runtime") == "MLX-LM")
261
+
262
+ if not mlx_available or not (mlx_vlm_available or mlx_lm_available):
263
+ payload.update({
264
+ "status": "runtime_not_installed",
265
+ "checked": False,
266
+ "supported": True,
267
+ "user_message": (
268
+ "Install the local MLX runtime before loading Gemma 4, or choose "
269
+ "the Gemma 4 GGUF route through Ollama, LM Studio, or llama.cpp."
270
+ ),
271
+ "alternatives": candidates[2:],
272
+ })
273
+ return payload
274
+
275
+ if model_type == "gemma4_unified" and not _module_available(GEMMA4_MLX_UNIFIED_MODULE):
276
+ payload.update({
277
+ "status": "runtime_update_needed",
278
+ "supported": False,
279
+ "reason_code": "mlx_vlm_missing_gemma4_unified_model",
280
+ "model_type": "gemma4_unified",
281
+ "missing_components": [GEMMA4_MLX_UNIFIED_MODULE],
282
+ "user_message": (
283
+ "Gemma 4 12B uses the gemma4_unified MLX format. The installed "
284
+ "MLX-VLM runtime does not include that loader, so this local "
285
+ "model cannot load until MLX-VLM is updated."
286
+ ),
287
+ "recovery_guidance": [
288
+ "Update the MLX runtime from Library/System setup, or run: pip install --upgrade 'mlx-vlm>=0.6.3'.",
289
+ "After the runtime update, re-open Models so Lattice can re-check this model.",
290
+ "Use Gemma 4 26B A4B locally or Gemma 4 12B GGUF through Ollama, LM Studio, or llama.cpp until then.",
291
+ ],
292
+ "alternatives": [
293
+ {"id": "mlx-community/gemma-4-26b-a4b-it-4bit", "name": "Gemma 4 26B A4B", "engine": "local_mlx"},
294
+ {"id": "ollama:hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M", "name": "Gemma 4 12B GGUF", "engine": "ollama"},
295
+ {"id": "lmstudio:ggml-org/gemma-4-12B-it-GGUF", "name": "Gemma 4 12B GGUF", "engine": "lmstudio"},
296
+ ],
297
+ "action": "Runtime update needed",
298
+ })
299
+ return payload
300
+
301
+ if mlx_vlm_available:
302
+ return payload
303
+
304
+ payload.update({
305
+ "status": "fallback_available",
306
+ "supported": True,
307
+ "reason_code": "mlx_vlm_missing_gemma4_standard_runtime",
308
+ "missing_components": ["mlx_vlm"],
309
+ "user_message": (
310
+ "MLX-VLM is not available for this Gemma 4 model. Lattice can use "
311
+ "the MLX-LM text fallback or a Gemma 4 GGUF local runtime."
312
+ ),
313
+ "recovery_guidance": [
314
+ "Use the MLX-LM text fallback for text chat.",
315
+ "Use a Gemma 4 GGUF model through Ollama, LM Studio, or llama.cpp if the MLX route fails.",
316
+ ],
317
+ "alternatives": [
318
+ {"id": candidate["load_id"], "name": candidate["runtime"], "engine": candidate["engine"]}
319
+ for candidate in candidates
320
+ if candidate.get("role") != "v3_primary"
321
+ ],
322
+ })
323
+ if mlx_lm_available:
324
+ payload["preferred_runtime"] = "MLX-LM fallback"
325
+ return payload
326
+
327
+
328
+ def friendly_model_runtime_error(
329
+ error: BaseException | str,
330
+ *,
331
+ model_id: Optional[str] = None,
332
+ engine: Optional[str] = None,
333
+ ) -> Dict[str, Any]:
334
+ """Convert loader exceptions into end-user recoverable error payloads."""
335
+ raw = str(error or "")
336
+ compat = model_runtime_compatibility(model_id or raw, engine=engine)
337
+ if not compat.get("supported", True):
338
+ return {
339
+ "status": compat.get("status") or "unsupported",
340
+ "model_id": model_id,
341
+ "engine": engine,
342
+ "user_message": compat.get("user_message") or (
343
+ "The selected model is not supported by the installed local runtime."
344
+ ),
345
+ "recovery_guidance": compat.get("recovery_guidance") or [
346
+ "Choose a recommended alternative model.",
347
+ "Update the local runtime and try validation again.",
348
+ ],
349
+ "alternatives": compat.get("alternatives") or [],
350
+ "missing_components": compat.get("missing_components") or [],
351
+ "action": compat.get("action"),
352
+ "reason_code": compat.get("reason_code") or "runtime_model_type_unsupported",
353
+ }
354
+ return {
355
+ "status": "load_failed",
356
+ "model_id": model_id,
357
+ "engine": engine,
358
+ "user_message": (
359
+ "The model could not be loaded. Check that the runtime is installed, "
360
+ "the model files are present, and try a recommended alternative if the issue continues."
361
+ ),
362
+ "recovery_guidance": [
363
+ "Open Models and run the setup flow again.",
364
+ "Confirm downloads were explicitly allowed for models that are not on this computer.",
365
+ "Try a recommended smaller local model if memory is low.",
366
+ ],
367
+ }
368
+
369
+
122
370
  # ── Postprocessing ────────────────────────────────────────────────────────────
123
371
 
124
372
  BAD_MARKERS = [
@@ -378,7 +626,9 @@ __all__ = [
378
626
  "FAMILY_PROFILES",
379
627
  "CompatProfile",
380
628
  "detect_model_family",
629
+ "friendly_model_runtime_error",
381
630
  "get_model_profile",
631
+ "model_runtime_compatibility",
382
632
  "fast_postprocess",
383
633
  "validate_smoke_response",
384
634
  "classify_smoke_response",