ltcai 3.6.0 → 4.0.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 (169) hide show
  1. package/README.md +11 -7
  2. package/docs/V4_BRAIN_ARCHITECTURE.md +322 -0
  3. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +509 -0
  4. package/docs/V4_IMPLEMENTATION_PLAN.md +470 -0
  5. package/docs/kg-schema.md +47 -53
  6. package/kg_schema.py +93 -10
  7. package/knowledge_graph.py +362 -33
  8. package/knowledge_graph_api.py +11 -127
  9. package/latticeai/__init__.py +1 -1
  10. package/latticeai/api/admin.py +1 -1
  11. package/latticeai/api/agents.py +7 -1
  12. package/latticeai/api/auth.py +27 -4
  13. package/latticeai/api/chat.py +112 -76
  14. package/latticeai/api/health.py +1 -1
  15. package/latticeai/api/hooks.py +1 -1
  16. package/latticeai/api/knowledge_graph.py +146 -0
  17. package/latticeai/api/local_files.py +1 -1
  18. package/latticeai/api/mcp.py +23 -11
  19. package/latticeai/api/memory.py +1 -1
  20. package/latticeai/api/models.py +1 -1
  21. package/latticeai/api/network.py +81 -0
  22. package/latticeai/api/realtime.py +1 -1
  23. package/latticeai/api/search.py +26 -2
  24. package/latticeai/api/security_dashboard.py +2 -3
  25. package/latticeai/api/setup.py +2 -2
  26. package/latticeai/api/static_routes.py +2 -4
  27. package/latticeai/api/tools.py +3 -0
  28. package/latticeai/api/workflow_designer.py +46 -0
  29. package/latticeai/api/workspace.py +71 -49
  30. package/latticeai/app_factory.py +1710 -0
  31. package/latticeai/brain/__init__.py +18 -0
  32. package/latticeai/brain/context.py +213 -0
  33. package/latticeai/brain/conversations.py +236 -0
  34. package/latticeai/brain/identity.py +175 -0
  35. package/latticeai/brain/memory.py +102 -0
  36. package/latticeai/brain/network.py +205 -0
  37. package/latticeai/core/agent.py +31 -7
  38. package/latticeai/core/audit.py +0 -7
  39. package/latticeai/core/config.py +1 -1
  40. package/latticeai/core/context_builder.py +1 -2
  41. package/latticeai/core/enterprise.py +1 -1
  42. package/latticeai/core/graph_curator.py +2 -2
  43. package/latticeai/core/marketplace.py +1 -1
  44. package/latticeai/core/mcp_registry.py +791 -0
  45. package/latticeai/core/model_compat.py +1 -1
  46. package/latticeai/core/model_resolution.py +0 -1
  47. package/latticeai/core/multi_agent.py +238 -4
  48. package/latticeai/core/security.py +1 -1
  49. package/latticeai/core/sessions.py +37 -7
  50. package/latticeai/core/workflow_engine.py +114 -2
  51. package/latticeai/core/workspace_os.py +58 -10
  52. package/latticeai/models/__init__.py +7 -0
  53. package/latticeai/models/router.py +779 -0
  54. package/latticeai/server_app.py +29 -1536
  55. package/latticeai/services/agent_runtime.py +1 -0
  56. package/latticeai/services/app_context.py +75 -14
  57. package/latticeai/services/ingestion.py +47 -0
  58. package/latticeai/services/kg_portability.py +33 -3
  59. package/latticeai/services/memory_service.py +39 -11
  60. package/latticeai/services/model_runtime.py +2 -5
  61. package/latticeai/services/platform_runtime.py +100 -23
  62. package/latticeai/services/search_service.py +17 -8
  63. package/latticeai/services/tool_dispatch.py +12 -2
  64. package/latticeai/services/triggers.py +241 -0
  65. package/latticeai/services/upload_service.py +37 -12
  66. package/latticeai/services/workspace_service.py +31 -0
  67. package/llm_router.py +29 -772
  68. package/ltcai_cli.py +1 -2
  69. package/mcp_registry.py +25 -788
  70. package/p_reinforce.py +124 -14
  71. package/package.json +9 -7
  72. package/scripts/bump_version.py +99 -0
  73. package/scripts/generate_diagrams.py +0 -1
  74. package/scripts/lint_v3.mjs +82 -18
  75. package/scripts/validate_release_artifacts.py +0 -1
  76. package/scripts/wheel_smoke.py +142 -0
  77. package/server.py +11 -7
  78. package/setup_wizard.py +1142 -0
  79. package/static/account.html +2 -4
  80. package/static/admin.html +3 -5
  81. package/static/chat.html +3 -6
  82. package/static/graph.html +2 -4
  83. package/static/sw.js +81 -52
  84. package/static/v3/asset-manifest.json +20 -19
  85. package/static/v3/css/{lattice.base.e4cdd05d.css → lattice.base.49deefb5.css} +1 -1
  86. package/static/v3/css/lattice.base.css +1 -1
  87. package/static/v3/css/{lattice.components.9b49d614.css → lattice.components.cde18231.css} +1 -1
  88. package/static/v3/css/lattice.components.css +1 -1
  89. package/static/v3/css/{lattice.shell.8fcc9d33.css → lattice.shell.29d36d85.css} +1 -1
  90. package/static/v3/css/lattice.shell.css +1 -1
  91. package/static/v3/css/{lattice.tokens.e7018963.css → lattice.tokens.304cbc40.css} +3 -0
  92. package/static/v3/css/lattice.tokens.css +3 -0
  93. package/static/v3/css/{lattice.views.22f69117.css → lattice.views.0a18b6c5.css} +2 -2
  94. package/static/v3/css/lattice.views.css +2 -2
  95. package/static/v3/index.html +3 -4
  96. package/static/v3/js/{app.c541f955.js → app.356e6452.js} +1 -1
  97. package/static/v3/js/core/{api.33d6320e.js → api.7a308b89.js} +1 -1
  98. package/static/v3/js/core/{routes.2ce3815a.js → routes.7222343d.js} +22 -22
  99. package/static/v3/js/core/routes.js +22 -22
  100. package/static/v3/js/core/{shell.8c163e0e.js → shell.a1657f20.js} +4 -4
  101. package/static/v3/js/core/shell.js +1 -1
  102. package/static/v3/js/core/{store.34ebd5e6.js → store.204a08b2.js} +1 -1
  103. package/static/v3/js/core/store.js +1 -1
  104. package/static/v3/js/views/graph-canvas.17c15d65.js +509 -0
  105. package/static/v3/js/views/graph-canvas.js +509 -0
  106. package/static/v3/js/views/{hybrid-search.b22b97e0.js → hybrid-search.2fb63ed9.js} +1 -2
  107. package/static/v3/js/views/hybrid-search.js +1 -2
  108. package/static/v3/js/views/{knowledge-graph.a96040a5.js → knowledge-graph.5e40cbeb.js} +33 -37
  109. package/static/v3/js/views/knowledge-graph.js +33 -37
  110. package/static/vendor/chart.umd.min.js +20 -0
  111. package/static/vendor/fonts/inter-latin-300-normal.woff2 +0 -0
  112. package/static/vendor/fonts/inter-latin-400-normal.woff2 +0 -0
  113. package/static/vendor/fonts/inter-latin-500-normal.woff2 +0 -0
  114. package/static/vendor/fonts/inter-latin-600-normal.woff2 +0 -0
  115. package/static/vendor/fonts/inter-latin-700-normal.woff2 +0 -0
  116. package/static/vendor/fonts/inter-latin-800-normal.woff2 +0 -0
  117. package/static/vendor/fonts/inter.css +44 -0
  118. package/static/vendor/icons/tabler-icons.min.css +4 -0
  119. package/static/vendor/icons/tabler-icons.woff2 +0 -0
  120. package/static/vendor/marked.min.js +69 -0
  121. package/static/workspace.html +2 -2
  122. package/telegram_bot.py +1 -2
  123. package/tools/commands.py +4 -2
  124. package/tools/computer.py +1 -1
  125. package/tools/documents.py +1 -3
  126. package/tools/filesystem.py +0 -4
  127. package/tools/knowledge.py +1 -3
  128. package/tools/network.py +1 -3
  129. package/codex_telegram_bot.py +0 -195
  130. package/docs/assets/v3.4.0/agent-run.png +0 -0
  131. package/docs/assets/v3.4.0/agents.png +0 -0
  132. package/docs/assets/v3.4.0/before/chat-before.png +0 -0
  133. package/docs/assets/v3.4.0/before/files-before.png +0 -0
  134. package/docs/assets/v3.4.0/chat.png +0 -0
  135. package/docs/assets/v3.4.0/connect-folder.png +0 -0
  136. package/docs/assets/v3.4.0/files.png +0 -0
  137. package/docs/assets/v3.4.0/home.png +0 -0
  138. package/docs/assets/v3.4.0/hooks-dispatch.png +0 -0
  139. package/docs/assets/v3.4.0/knowledge-graph.png +0 -0
  140. package/docs/assets/v3.4.0/local-agent.png +0 -0
  141. package/docs/assets/v3.4.0/memory.png +0 -0
  142. package/docs/assets/v3.4.0/settings.png +0 -0
  143. package/docs/assets/v3.4.0/vision-input.png +0 -0
  144. package/docs/assets/v3.4.0/workflows.png +0 -0
  145. package/docs/assets/v3.4.1/e2e_runtime_log.txt +0 -42
  146. package/docs/assets/v3.4.1/hooks-dispatch.png +0 -0
  147. package/docs/assets/v3.4.1/local-agent.png +0 -0
  148. package/docs/images/admin-dashboard.png +0 -0
  149. package/docs/images/architecture.png +0 -0
  150. package/docs/images/enterprise.png +0 -0
  151. package/docs/images/graph.png +0 -0
  152. package/docs/images/hero.gif +0 -0
  153. package/docs/images/knowledge-graph.png +0 -0
  154. package/docs/images/lattice-ai-demo.gif +0 -0
  155. package/docs/images/lattice-ai-hero.png +0 -0
  156. package/docs/images/logo.svg +0 -33
  157. package/docs/images/mobile-responsive.png +0 -0
  158. package/docs/images/model-recommendation.png +0 -0
  159. package/docs/images/onboarding.png +0 -0
  160. package/docs/images/organization.png +0 -0
  161. package/docs/images/pipeline.png +0 -0
  162. package/docs/images/screenshot-admin.png +0 -0
  163. package/docs/images/screenshot-chat.png +0 -0
  164. package/docs/images/screenshot-graph.png +0 -0
  165. package/docs/images/skills.png +0 -0
  166. package/docs/images/workspace-dark.png +0 -0
  167. package/docs/images/workspace-light.png +0 -0
  168. package/docs/images/workspace.png +0 -0
  169. package/requirements.txt +0 -16
@@ -18,7 +18,7 @@ from pathlib import Path
18
18
  from typing import Any, Callable, Dict, Iterable, List, Optional
19
19
 
20
20
 
21
- WORKSPACE_OS_VERSION = "3.6.0"
21
+ WORKSPACE_OS_VERSION = "4.0.0"
22
22
 
23
23
  # Workspace types separate single-user Personal workspaces from shared
24
24
  # Organization workspaces. Both keep the same local-first JSON store; the type
@@ -430,7 +430,10 @@ class WorkspaceOSStore:
430
430
  "version": WORKSPACE_OS_VERSION,
431
431
  "identity": state.get("identity"),
432
432
  "active_workspace": state.get("active_workspace"),
433
- "workspaces": state.get("workspaces"),
433
+ # The raw workspace registry (with member lists) must not leak to
434
+ # non-members; WorkspaceService.summary() adds a membership-filtered
435
+ # "workspace_registry" instead.
436
+ "workspace_count": len(state.get("workspaces") or {}),
434
437
  "navigation": list(WORKSPACE_AREAS),
435
438
  "feature_flags": state.get("feature_flags"),
436
439
  "updated_at": state.get("updated_at"),
@@ -1229,15 +1232,26 @@ class WorkspaceOSStore:
1229
1232
  ]
1230
1233
  return {"query": query, "memories": memories[: max(1, min(limit, 100))]}
1231
1234
 
1235
+ def get_memory(self, memory_id: str) -> Dict[str, Any]:
1236
+ record = next(
1237
+ (item for item in _listify(self.load_state().get("memories")) if item.get("id") == memory_id),
1238
+ None,
1239
+ )
1240
+ if record is None:
1241
+ raise FileNotFoundError(memory_id)
1242
+ return record
1243
+
1232
1244
  def delete_memory(self, memory_id: str) -> Dict[str, Any]:
1233
1245
  state = self.load_state()
1234
1246
  memories = _listify(state.get("memories"))
1235
- kept = [item for item in memories if item.get("id") != memory_id]
1236
- if len(kept) == len(memories):
1247
+ target = next((item for item in memories if item.get("id") == memory_id), None)
1248
+ if target is None:
1237
1249
  raise FileNotFoundError(memory_id)
1238
- state["memories"] = kept
1250
+ state["memories"] = [item for item in memories if item.get("id") != memory_id]
1239
1251
  self.save_state(state)
1240
- self.record_timeline_event("memory", "memory_deleted", {"memory_id": memory_id})
1252
+ self.record_timeline_event(
1253
+ "memory", "memory_deleted", {"memory_id": memory_id}, workspace_id=target.get("workspace_id")
1254
+ )
1241
1255
  return {"status": "ok", "memory_id": memory_id}
1242
1256
 
1243
1257
  def create_memory_snapshot(
@@ -1306,12 +1320,15 @@ class WorkspaceOSStore:
1306
1320
  memory_snapshots: Optional[List[Dict[str, Any]]] = None,
1307
1321
  graph: Any = None,
1308
1322
  workspace_id: Optional[str] = None,
1323
+ mode: str = "simulation",
1309
1324
  ) -> Dict[str, Any]:
1310
1325
  state = self.load_state()
1311
1326
  resolved_workspace = self._resolve_scope(workspace_id, state)
1312
1327
  run = {
1313
1328
  "id": f"agent-run-{_json_hash([agent_id, input_text, output_text, _now()])[:16]}",
1329
+ "record_schema_version": 2,
1314
1330
  "agent_id": agent_id,
1331
+ "mode": mode,
1315
1332
  "status": status,
1316
1333
  "input": input_text,
1317
1334
  "output_preview": output_text[:1000],
@@ -1328,14 +1345,19 @@ class WorkspaceOSStore:
1328
1345
  "memory_snapshots": memory_snapshots or [],
1329
1346
  "created_at": _now(),
1330
1347
  }
1331
- if graph is not None:
1348
+ if mode == "simulation":
1349
+ # Simulated runs are replay scaffolding, not experiences — they must
1350
+ # never enter the knowledge graph as real provenance.
1351
+ run["graph_node_id"] = None
1352
+ run["graph_skipped"] = "simulation runs are not recorded in the knowledge graph"
1353
+ elif graph is not None:
1332
1354
  try:
1333
1355
  ingested = graph.ingest_event(
1334
1356
  "AgentRun",
1335
1357
  f"{agent_id} {status}",
1336
1358
  user_email=user_email,
1337
1359
  source="workspace_os",
1338
- metadata={"run_id": run["id"], "agent_id": agent_id, "status": status},
1360
+ metadata={"run_id": run["id"], "agent_id": agent_id, "status": status, "mode": mode},
1339
1361
  )
1340
1362
  run["graph_node_id"] = ingested.get("node_id")
1341
1363
  except Exception as exc:
@@ -1427,14 +1449,18 @@ class WorkspaceOSStore:
1427
1449
  user_email: Optional[str] = None,
1428
1450
  graph: Any = None,
1429
1451
  workspace_id: Optional[str] = None,
1452
+ mode: str = "simulation",
1453
+ pause: Optional[Dict[str, Any]] = None,
1430
1454
  ) -> Dict[str, Any]:
1431
1455
  """Persist a Workflow Designer execution into local-first run history."""
1432
1456
  state = self.load_state()
1433
1457
  resolved_workspace = self._resolve_scope(workspace_id, state)
1434
1458
  run = {
1435
1459
  "id": f"workflow-run-{_json_hash([workflow_id, name, status, _now()])[:16]}",
1460
+ "record_schema_version": 2,
1436
1461
  "workflow_id": workflow_id,
1437
1462
  "name": name or "workflow",
1463
+ "mode": mode,
1438
1464
  "status": status,
1439
1465
  "timeline": timeline or [],
1440
1466
  "outputs": outputs or {},
@@ -1442,14 +1468,21 @@ class WorkspaceOSStore:
1442
1468
  "workspace_id": resolved_workspace,
1443
1469
  "created_at": _now(),
1444
1470
  }
1445
- if graph is not None:
1471
+ if pause:
1472
+ run["pause"] = pause
1473
+ if mode == "simulation":
1474
+ # Record-only node runners do no real work; their runs must not be
1475
+ # written into the knowledge graph as if they were real executions.
1476
+ run["graph_node_id"] = None
1477
+ run["graph_skipped"] = "simulation runs are not recorded in the knowledge graph"
1478
+ elif graph is not None:
1446
1479
  try:
1447
1480
  ingested = graph.ingest_event(
1448
1481
  "WorkflowRun",
1449
1482
  f"{run['name']} {status}",
1450
1483
  user_email=user_email,
1451
1484
  source="workspace_os",
1452
- metadata={"run_id": run["id"], "workflow_id": workflow_id, "status": status},
1485
+ metadata={"run_id": run["id"], "workflow_id": workflow_id, "status": status, "mode": mode},
1453
1486
  )
1454
1487
  run["graph_node_id"] = ingested.get("node_id")
1455
1488
  except Exception as exc:
@@ -1478,6 +1511,21 @@ class WorkspaceOSStore:
1478
1511
  runs = [run for run in runs if run.get("workflow_id") == workflow_id]
1479
1512
  return {"runs": list(reversed(runs[-max(1, min(limit, 300)):]))}
1480
1513
 
1514
+ def mark_workflow_run_resolved(
1515
+ self, run_id: str, *, resumed_run_id: str, approved: bool,
1516
+ workspace_id: Optional[str] = None,
1517
+ ) -> Dict[str, Any]:
1518
+ """Close out a paused run after its approval decision (one decision only)."""
1519
+ state = self.load_state()
1520
+ run = next((item for item in _listify(state.get("workflow_runs")) if item.get("id") == run_id), None)
1521
+ if run is None or (workspace_id and self._record_workspace(run) != str(workspace_id)):
1522
+ raise FileNotFoundError(run_id)
1523
+ run["status"] = "resumed" if approved else "denied"
1524
+ run["resolved_at"] = _now()
1525
+ run["resumed_run_id"] = resumed_run_id
1526
+ self.save_state(state)
1527
+ return run
1528
+
1481
1529
  def get_workflow_run(self, run_id: str, workspace_id: Optional[str] = None) -> Dict[str, Any]:
1482
1530
  run = next((item for item in _listify(self.load_state().get("workflow_runs")) if item.get("id") == run_id), None)
1483
1531
  if not run or (workspace_id and self._record_workspace(run) != str(workspace_id)):
@@ -0,0 +1,7 @@
1
+ """Model/provider boundary package.
2
+
3
+ ``latticeai.models.router`` hosts the multi-engine LLM router (MLX-VLM,
4
+ Ollama, vLLM, llama.cpp, LM Studio, OpenAI-compatible providers), relocated
5
+ from the root ``llm_router.py`` in v4 (T2). The root module remains as a
6
+ deprecation shim.
7
+ """