ltcai 8.1.0 → 8.3.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 (123) hide show
  1. package/README.md +29 -48
  2. package/docs/CHANGELOG.md +70 -874
  3. package/docs/COMMUNITY_AND_PLUGINS.md +43 -0
  4. package/docs/DEVELOPMENT.md +26 -3
  5. package/docs/LEGACY_COMPATIBILITY.md +23 -1
  6. package/docs/ONBOARDING.md +38 -0
  7. package/docs/TRUST_MODEL.md +26 -26
  8. package/docs/WHY_LATTICE.md +29 -25
  9. package/docs/kg-schema.md +7 -5
  10. package/lattice_brain/__init__.py +1 -1
  11. package/lattice_brain/graph/ingest.py +40 -8
  12. package/lattice_brain/graph/schema.py +6 -0
  13. package/lattice_brain/runtime/agent_runtime.py +22 -37
  14. package/lattice_brain/runtime/multi_agent.py +1 -1
  15. package/lattice_brain/workflow.py +26 -1
  16. package/latticeai/__init__.py +1 -1
  17. package/latticeai/api/computer_use.py +2 -0
  18. package/latticeai/api/knowledge_graph.py +33 -0
  19. package/latticeai/api/local_files.py +2 -0
  20. package/latticeai/api/memory.py +13 -0
  21. package/latticeai/api/tools.py +1 -0
  22. package/latticeai/api/workflow_designer.py +5 -4
  23. package/latticeai/app_factory.py +30 -28
  24. package/latticeai/brain/__init__.py +1 -1
  25. package/latticeai/core/config.py +10 -0
  26. package/latticeai/core/legacy_compatibility.py +174 -0
  27. package/latticeai/core/marketplace.py +1 -1
  28. package/latticeai/core/tool_registry.py +10 -0
  29. package/latticeai/core/workspace_graph_trace.py +132 -0
  30. package/latticeai/core/workspace_memory.py +75 -0
  31. package/latticeai/core/workspace_os.py +135 -1283
  32. package/latticeai/core/workspace_os_utils.py +132 -0
  33. package/latticeai/core/workspace_permissions.py +99 -0
  34. package/latticeai/core/workspace_plugins.py +97 -0
  35. package/latticeai/core/workspace_runs.py +612 -0
  36. package/latticeai/core/workspace_skills.py +114 -0
  37. package/latticeai/core/workspace_snapshots.py +195 -0
  38. package/latticeai/core/workspace_timeline.py +107 -0
  39. package/latticeai/runtime/audit_runtime.py +64 -0
  40. package/latticeai/runtime/config_runtime.py +4 -0
  41. package/latticeai/runtime/sso_runtime.py +52 -0
  42. package/latticeai/services/architecture_readiness.py +37 -3
  43. package/latticeai/services/memory_service.py +215 -7
  44. package/latticeai/services/model_capability_registry.py +68 -0
  45. package/latticeai/services/model_engines.py +594 -0
  46. package/latticeai/services/model_loading.py +482 -0
  47. package/latticeai/services/model_recommendation.py +5 -0
  48. package/latticeai/services/model_runtime.py +131 -875
  49. package/latticeai/services/product_readiness.py +41 -18
  50. package/llm_router.py +7 -28
  51. package/mcp_registry.py +7 -24
  52. package/package.json +1 -1
  53. package/scripts/pts-claudecode-discord-bridge.mjs +2 -1
  54. package/src-tauri/Cargo.lock +1 -1
  55. package/src-tauri/Cargo.toml +1 -1
  56. package/src-tauri/tauri.conf.json +1 -1
  57. package/static/app/asset-manifest.json +11 -11
  58. package/static/app/assets/{Act-BOO66G-c.js → Act-D5mo4tE4.js} +1 -1
  59. package/static/app/assets/{Brain-C6lEYiD7.js → Brain-BVWyQw8A.js} +1 -1
  60. package/static/app/assets/{Capture-TATXBRDw.js → Capture-C1R6GT0t.js} +1 -1
  61. package/static/app/assets/{Library-DK4FIp8a.js → Library-C2wIxpTs.js} +1 -1
  62. package/static/app/assets/{System-Bgs6Ql7x.js → System-DE5GRyQR.js} +1 -1
  63. package/static/app/assets/index-CoiuIFFP.js +16 -0
  64. package/static/app/assets/index-ty1iGgZu.css +2 -0
  65. package/static/app/assets/{primitives-B70WOra0.js → primitives-BdsUNXa6.js} +1 -1
  66. package/static/app/assets/{textarea-Czrd9gwM.js → textarea-e7qaj6Hm.js} +1 -1
  67. package/static/app/index.html +2 -2
  68. package/static/sw.js +1 -1
  69. package/tools/__init__.py +2 -1
  70. package/tools/computer.py +21 -0
  71. package/docs/CARRYOVER_AUDIT_v3.6.0.md +0 -61
  72. package/docs/HANDOVER_v3.6.0.md +0 -46
  73. package/docs/RUNTIME_HOOK_COVERAGE_v3.5.0.md +0 -56
  74. package/docs/RUNTIME_HOOK_COVERAGE_v3.6.0.md +0 -49
  75. package/docs/V2_ARCHITECTURE.md +0 -561
  76. package/docs/V3_2_AUDIT.md +0 -82
  77. package/docs/V3_BACKEND_ARCHITECTURE.md +0 -138
  78. package/docs/V3_FRONTEND.md +0 -146
  79. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +0 -65
  80. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +0 -70
  81. package/docs/V4_1_VALIDATION_REPORT.md +0 -47
  82. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +0 -97
  83. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +0 -91
  84. package/docs/V4_2_VALIDATION_REPORT.md +0 -89
  85. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +0 -174
  86. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +0 -81
  87. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +0 -75
  88. package/docs/V4_3_2_GRAPH_UX_REPORT.md +0 -48
  89. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +0 -209
  90. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +0 -57
  91. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +0 -63
  92. package/docs/V4_3_2_VALIDATION_REPORT.md +0 -97
  93. package/docs/V4_3_3_VALIDATION_REPORT.md +0 -46
  94. package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +0 -69
  95. package/docs/V4_3_PRIVACY_AUDIT.md +0 -60
  96. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +0 -53
  97. package/docs/V4_3_VALIDATION_REPORT.md +0 -58
  98. package/docs/V4_4_0_EXTRACTION_REPORT.md +0 -239
  99. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +0 -49
  100. package/docs/V4_5_0_GRAPH_UX_REPORT.md +0 -34
  101. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +0 -40
  102. package/docs/V4_5_0_ONBOARDING_REPORT.md +0 -31
  103. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +0 -49
  104. package/docs/V4_5_0_VALIDATION_REPORT.md +0 -60
  105. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +0 -33
  106. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +0 -37
  107. package/docs/V4_5_1_NAVIGATION_REPORT.md +0 -37
  108. package/docs/V4_5_1_ONBOARDING_REPORT.md +0 -29
  109. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +0 -61
  110. package/docs/V4_5_1_RC_ARTIFACTS.md +0 -44
  111. package/docs/V4_5_1_UX_REPORT.md +0 -45
  112. package/docs/V4_5_1_VALIDATION_REPORT.md +0 -55
  113. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +0 -30
  114. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +0 -72
  115. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +0 -42
  116. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +0 -42
  117. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +0 -49
  118. package/docs/V4_7_2_INTUITIVE_BRAIN_UX_REPORT.md +0 -62
  119. package/docs/V4_BRAIN_ARCHITECTURE.md +0 -322
  120. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +0 -555
  121. package/docs/V4_IMPLEMENTATION_PLAN.md +0 -470
  122. package/static/app/assets/index-Bvv79nre.js +0 -16
  123. package/static/app/assets/index-Dslqglia.css +0 -2
@@ -27,7 +27,7 @@ from dataclasses import dataclass, field
27
27
  from datetime import datetime
28
28
  from typing import Any, Callable, Dict, List, Optional
29
29
 
30
- from lattice_brain.runtime.contracts import workflow_run_contract
30
+ from lattice_brain.runtime.contracts import runtime_boundary_contract, workflow_run_contract
31
31
 
32
32
 
33
33
  WORKFLOW_ENGINE_VERSION = "2.2.0"
@@ -106,6 +106,11 @@ def normalize_definition(workflow: Dict[str, Any]) -> Dict[str, Any]:
106
106
  }
107
107
 
108
108
 
109
+ def legacy_steps_from_nodes(nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
110
+ """Return the compact legacy ``steps`` projection for node workflows."""
111
+ return [{"action": node.get("type"), "node": node.get("id")} for node in nodes]
112
+
113
+
109
114
  def validate_definition(workflow: Dict[str, Any]) -> List[str]:
110
115
  """Return a list of validation errors ([] means valid)."""
111
116
  errors: List[str] = []
@@ -269,6 +274,26 @@ class WorkflowEngine:
269
274
  # against the workflow lifecycle actually executes.
270
275
  self.hooks = hooks
271
276
 
277
+ def boundary(self) -> Dict[str, Any]:
278
+ return runtime_boundary_contract(
279
+ name="WorkflowEngine",
280
+ runtime="workflow",
281
+ entrypoint="lattice_brain.workflow.WorkflowEngine",
282
+ surface="/workflows",
283
+ owns="typed workflow validation, execution, approval suspension, resume, export, and import",
284
+ )
285
+
286
+ def config(self) -> Dict[str, Any]:
287
+ return {
288
+ "version": WORKFLOW_ENGINE_VERSION,
289
+ "boundary": self.boundary(),
290
+ "node_types": list(NODE_TYPES),
291
+ "runner_families": dict(_RUNNER_FOR),
292
+ "max_steps": _MAX_STEPS,
293
+ "approval_suspend": True,
294
+ "legacy_steps_projection": "lattice_brain.workflow.legacy_steps_from_nodes",
295
+ }
296
+
272
297
  def run(self, workflow: Dict[str, Any], *, inputs: Optional[Dict[str, Any]] = None) -> WorkflowRun:
273
298
  definition = normalize_definition(workflow)
274
299
  errors = validate_definition(definition)
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "8.1.0"
3
+ __version__ = "8.3.0"
@@ -42,12 +42,14 @@ Available actions:
42
42
  - computer_scroll: {"action":"computer_scroll","args":{"x":500,"y":300,"direction":"down","clicks":3}}
43
43
  - computer_move: {"action":"computer_move","args":{"x":500,"y":300}}
44
44
  - computer_drag: {"action":"computer_drag","args":{"x1":100,"y1":100,"x2":500,"y2":500}}
45
+ - vision_analyze: {"action":"vision_analyze","args":{"image_b64": "...", "prompt": "What do you see on screen?"}} — use after screenshot to let VLM describe/answer about the image (only when multimodal model loaded)
45
46
  - final: {"action":"final","message":"Korean summary of what was accomplished"}
46
47
 
47
48
  Rules:
48
49
  - Respond with exactly ONE JSON object. No markdown, no extra text.
49
50
  - Do not take screenshots for simple app launch, URL opening, keyboard shortcuts, or non-visual tasks.
50
51
  - Take a screenshot before coordinate-based clicks/drags or when the task explicitly asks you to inspect the screen.
52
+ - After screenshot, prefer vision_analyze (with good prompt) over raw b64 in next step when you need to understand what is on screen (especially with multimodal/VLM loaded).
51
53
  - After coordinate-based clicking or typing into an unknown focused field, take a screenshot only if verification is necessary.
52
54
  - Use coordinates relative to the screen (0,0 is top-left).
53
55
  - If a UI element is not visible, scroll or search for it first.
@@ -13,6 +13,7 @@ from fastapi import APIRouter, HTTPException, Request
13
13
  from pydantic import BaseModel
14
14
 
15
15
  from latticeai.api.ui_redirects import app_redirect
16
+ from lattice_brain.ingestion import IngestionItem
16
17
 
17
18
 
18
19
  class KnowledgeGraphIngestRequest(BaseModel):
@@ -61,6 +62,7 @@ def create_knowledge_graph_router(
61
62
  require_user: Callable[[Request], str],
62
63
  static_dir: Path,
63
64
  allowed_workspaces_for: Optional[Callable[[Optional[str]], Any]] = None,
65
+ ingestion_pipeline: Any = None,
64
66
  ) -> APIRouter:
65
67
  router = APIRouter()
66
68
 
@@ -191,6 +193,37 @@ def create_knowledge_graph_router(
191
193
  if event_type not in {"message", "ai_response", "note"}:
192
194
  raise HTTPException(status_code=400, detail="지원하는 type: message, ai_response, note")
193
195
  role = req.role or ("assistant" if event_type == "ai_response" else "user")
196
+ if ingestion_pipeline is not None:
197
+ source_type = "chat_message" if event_type in {"message", "ai_response"} else "note"
198
+ result = ingestion_pipeline.ingest(
199
+ IngestionItem(
200
+ source_type=source_type,
201
+ title=req.title,
202
+ text=req.content,
203
+ source_uri=req.source,
204
+ owner=req.user_email or current_user,
205
+ workspace_id=workspace_id,
206
+ conversation_id=req.conversation_id,
207
+ metadata={
208
+ "type": req.type,
209
+ "role": role,
210
+ "source": req.source or "mcp",
211
+ "user_nickname": req.user_nickname,
212
+ "raw": {
213
+ "type": req.type,
214
+ "title": req.title,
215
+ "content": req.content,
216
+ "workspace_id": workspace_id,
217
+ "metadata": req.metadata or {},
218
+ },
219
+ **(req.metadata or {}),
220
+ },
221
+ ),
222
+ user_email=req.user_email or current_user,
223
+ )
224
+ if result.status != "ok":
225
+ raise HTTPException(status_code=500, detail=result.detail or result.status)
226
+ return result.as_dict()
194
227
  return kg.ingest_message(
195
228
  role,
196
229
  req.content,
@@ -46,6 +46,7 @@ def create_local_files_router(
46
46
  require_graph,
47
47
  static_dir: Path,
48
48
  local_kg_watcher,
49
+ ingestion_pipeline=None,
49
50
  hooks=None,
50
51
  data_dir: Optional[Path] = None,
51
52
  allowed_workspaces_for=None,
@@ -212,6 +213,7 @@ def create_local_files_router(
212
213
  require_user=require_user,
213
214
  static_dir=static_dir,
214
215
  allowed_workspaces_for=allowed_workspaces_for,
216
+ ingestion_pipeline=ingestion_pipeline,
215
217
  )
216
218
  )
217
219
 
@@ -58,6 +58,19 @@ def create_memory_router(
58
58
  scope = gate_read(request)
59
59
  return service.brain_quality_summary(user_email=user, workspace_id=scope)
60
60
 
61
+ @router.get("/api/memory/brain-brief")
62
+ async def brain_brief(request: Request, q: str = "", limit: int = 3):
63
+ user = require_user(request)
64
+ scope = gate_read(request)
65
+ active_model = active_model_getter() if active_model_getter else ""
66
+ return service.brain_brief(
67
+ user_email=user,
68
+ workspace_id=scope,
69
+ active_model=active_model,
70
+ recall_query=q,
71
+ limit=limit,
72
+ )
73
+
61
74
  @router.get("/api/memory/brain-proof")
62
75
  async def brain_proof(request: Request, q: str = "", limit: int = 3):
63
76
  user = require_user(request)
@@ -488,6 +488,7 @@ def create_tools_router(
488
488
  require_graph=_require_graph,
489
489
  static_dir=STATIC_DIR,
490
490
  local_kg_watcher=LOCAL_KG_WATCHER,
491
+ ingestion_pipeline=ingestion_pipeline,
491
492
  hooks=HOOKS,
492
493
  data_dir=DATA_DIR,
493
494
  allowed_workspaces_for=allowed_workspaces_for,
@@ -73,10 +73,11 @@ def create_workflow_designer_router(
73
73
  ) -> APIRouter:
74
74
  from lattice_brain.workflow import (
75
75
  WorkflowEngine,
76
+ WorkflowError,
76
77
  validate_definition,
77
78
  export_workflow,
78
79
  import_workflow,
79
- WorkflowError,
80
+ legacy_steps_from_nodes,
80
81
  )
81
82
 
82
83
  router = APIRouter()
@@ -101,7 +102,7 @@ def create_workflow_designer_router(
101
102
  raise HTTPException(status_code=400, detail={"validation_errors": errors})
102
103
  workflow = store.create_workflow(
103
104
  name=req.name,
104
- steps=[{"action": n.get("type"), "node": n.get("id")} for n in req.nodes],
105
+ steps=legacy_steps_from_nodes(req.nodes),
105
106
  nodes=req.nodes,
106
107
  metadata=req.metadata,
107
108
  user_email=current_user or None,
@@ -293,7 +294,7 @@ def create_workflow_designer_router(
293
294
  raise HTTPException(status_code=400, detail={"validation_errors": errors})
294
295
  workflow = store.create_workflow(
295
296
  name=definition["name"],
296
- steps=[{"action": n.get("type"), "node": n.get("id")} for n in definition["nodes"]],
297
+ steps=legacy_steps_from_nodes(definition["nodes"]),
297
298
  nodes=definition["nodes"],
298
299
  metadata=definition["metadata"],
299
300
  user_email=current_user or None,
@@ -343,7 +344,7 @@ def create_workflow_designer_router(
343
344
  raise HTTPException(status_code=400, detail=str(exc)) from exc
344
345
  workflow = store.create_workflow(
345
346
  name=definition["name"],
346
- steps=[{"action": n.get("type"), "node": n.get("id")} for n in definition["nodes"]],
347
+ steps=legacy_steps_from_nodes(definition["nodes"]),
347
348
  nodes=definition["nodes"],
348
349
  metadata=definition.get("metadata") or {},
349
350
  user_email=current_user or None,
@@ -44,6 +44,8 @@ from latticeai.runtime.platform_services_runtime import (
44
44
  from latticeai.runtime.platform_runtime_wiring import build_platform_automation_runtime
45
45
  from latticeai.runtime.persistence_runtime import build_persistence_runtime
46
46
  from latticeai.runtime.review_wiring import build_review_run_now_runner
47
+ from latticeai.runtime.sso_runtime import build_sso_runtime
48
+ from latticeai.runtime.audit_runtime import build_audit_runtime
47
49
  from latticeai.runtime.router_registration import (
48
50
  build_auth_admin_security_router_bundle,
49
51
  build_static_routes_bundle,
@@ -93,6 +95,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
93
95
 
94
96
  from latticeai.models.router import LLMRouter, normalize_branding
95
97
  from lattice_brain._kg_common import set_llm_router
98
+ from lattice_brain.graph.schema import set_embed_dim
96
99
  from latticeai.core.security import (
97
100
  hash_password,
98
101
  verify_password,
@@ -105,7 +108,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
105
108
  )
106
109
  from latticeai.core.audit import (
107
110
  get_audit_log as _get_audit_log,
108
- append_audit_event as _append_audit_event,
109
111
  classify_sensitive_message as _classify_sensitive_message,
110
112
  build_sensitivity_report as _build_sensitivity_report,
111
113
  build_admin_audit_report as _build_admin_audit_report,
@@ -249,29 +251,12 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
249
251
  SSO_CLIENT_SECRET = _security_runtime["SSO_CLIENT_SECRET"]
250
252
  SSO_REDIRECT_URI = _security_runtime["SSO_REDIRECT_URI"]
251
253
  SSO_PROVIDER_NAME = _security_runtime["SSO_PROVIDER_NAME"]
252
- _sso_discovery_cache: Optional[Dict] = None
253
- _sso_discovery_cache_url: str = ""
254
- _sso_states: Dict[str, float] = {} # state → timestamp (CSRF protection)
255
254
 
256
- async def _get_sso_discovery() -> Optional[Dict]:
257
- nonlocal _sso_discovery_cache, _sso_discovery_cache_url
258
- settings = get_sso_settings()
259
- discovery_url = settings.get("discovery_url", "")
260
- if _sso_discovery_cache and _sso_discovery_cache_url == discovery_url:
261
- return _sso_discovery_cache
262
- if not discovery_url:
263
- return None
264
- try:
265
- import httpx as _httpx
266
- async with _httpx.AsyncClient() as c:
267
- r = await c.get(discovery_url, timeout=10)
268
- r.raise_for_status()
269
- _sso_discovery_cache = r.json()
270
- _sso_discovery_cache_url = discovery_url
271
- except Exception as e:
272
- logging.warning("SSO discovery failed: %s", e)
273
- return None
274
- return _sso_discovery_cache
255
+ # SSO cache + discovery built after get_sso_settings is defined (see below)
256
+ _sso_discovery_cache = None
257
+ _sso_discovery_cache_url = ""
258
+ _sso_states: Dict[str, float] = {}
259
+ _get_sso_discovery = None # filled after def below
275
260
 
276
261
  # ── Password hashing — used directly from latticeai.core.security ──────────────
277
262
  # (hash_password / verify_password are imported above; no local wrapper needed)
@@ -355,6 +340,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
355
340
  extra={"target": CONFIG.embedding_custom_target},
356
341
  probe=_embedding_provider not in {"", "hash", "local", "fallback"},
357
342
  )
343
+ set_embed_dim(int(getattr(EMBEDDER, "dim", None) or _embedding_dim or 384))
358
344
  if EMBEDDER.fell_back:
359
345
  logging.warning("Embedding provider %s unavailable: %s", EMBEDDER.requested, EMBEDDER.detail)
360
346
  STORAGE_ENGINE = storage_from_env(os.environ, data_dir=DATA_DIR) if ENABLE_GRAPH else None
@@ -479,6 +465,17 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
479
465
  def get_sso_settings() -> Dict[str, object]:
480
466
  return load_sso_config()
481
467
 
468
+ # Build SSO runtime (cache + helper) now that get_sso_settings exists in scope.
469
+ if _get_sso_discovery is None:
470
+ _sso = build_sso_runtime(
471
+ get_sso_settings=get_sso_settings,
472
+ logging=logging,
473
+ )
474
+ _sso_discovery_cache = _sso["_sso_discovery_cache"]
475
+ _sso_discovery_cache_url = _sso["_sso_discovery_cache_url"]
476
+ _sso_states = _sso["_sso_states"]
477
+ _get_sso_discovery = _sso["_get_sso_discovery"]
478
+
482
479
  def public_sso_config(config: Optional[Dict[str, object]] = None) -> Dict[str, object]:
483
480
  cfg = config or get_sso_settings()
484
481
  return {
@@ -558,11 +555,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
558
555
 
559
556
  _history_lock = threading.Lock()
560
557
 
561
- def get_audit_log() -> List[Dict]:
562
- return _get_audit_log(AUDIT_FILE)
563
-
564
- def append_audit_event(event_type: str, **payload) -> None:
565
- _append_audit_event(AUDIT_FILE, event_type, **payload)
558
+ # audit build moved after redact_secret_text is defined (see below)
566
559
 
567
560
  def save_to_history(
568
561
  role: str,
@@ -634,6 +627,15 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
634
627
  def redact_secret_text(text: str) -> str:
635
628
  return _redact_secret_text(text)
636
629
 
630
+ # Build audit now that redact is available
631
+ _audit_rt = build_audit_runtime(
632
+ audit_file=AUDIT_FILE,
633
+ logging=logging,
634
+ redact_fn=redact_secret_text,
635
+ )
636
+ get_audit_log = _audit_rt["get_audit_log"]
637
+ append_audit_event = _audit_rt["append_audit_event"]
638
+
637
639
  def get_history():
638
640
  try:
639
641
  return CONVERSATIONS.history()
@@ -22,7 +22,7 @@ warnings.warn(
22
22
  "latticeai.brain is deprecated; import lattice_brain instead",
23
23
  DeprecationWarning,
24
24
  stacklevel=2,
25
- )
25
+ ) # one-time per import site in practice; shims remain for compat surface
26
26
 
27
27
  __all__ = [
28
28
  "AssembledContext",
@@ -26,6 +26,8 @@ from typing import List, Mapping, Optional
26
26
  from latticeai.core.security import host_is_loopback
27
27
 
28
28
 
29
+ __all__ = ["Config", "_value", "_str", "_bool", "_int"]
30
+
29
31
  def _value(env: Mapping[str, str], key: str, default: str = "") -> str:
30
32
  """Mirror the legacy ``env_value``: ``getenv(key) or default or ""`` (no strip)."""
31
33
  return env.get(key) or default or ""
@@ -68,6 +70,7 @@ class Config:
68
70
  host: str
69
71
  port: int
70
72
  network_exposed: bool
73
+ timezone: str
71
74
 
72
75
  # ── feature flags ───────────────────────────────────────────────
73
76
  enable_telegram: bool
@@ -93,6 +96,9 @@ class Config:
93
96
  local_model: str
94
97
  local_draft_model: str
95
98
  auto_read_chat_paths: bool
99
+ max_local_models: int
100
+ allow_model_downloads: bool
101
+ model_download_timeout: int
96
102
 
97
103
  # ── embeddings (retrieval vector signal) ────────────────────────
98
104
  embedding_provider: str
@@ -165,6 +171,7 @@ class Config:
165
171
  host=host,
166
172
  port=port,
167
173
  network_exposed=network_exposed,
174
+ timezone=_value(env, "LATTICE_TZ", "UTC") or "UTC",
168
175
  enable_telegram=_bool(env, "LATTICEAI_ENABLE_TELEGRAM", default=False),
169
176
  enable_graph=_bool(env, "LATTICEAI_ENABLE_GRAPH", default=True),
170
177
  autoload_models=_bool(env, "LATTICEAI_AUTOLOAD_MODELS", default=is_public),
@@ -184,6 +191,9 @@ class Config:
184
191
  local_model=local_model,
185
192
  local_draft_model=_value(env, "LATTICEAI_LOCAL_DRAFT_MODEL", ""),
186
193
  auto_read_chat_paths=_bool(env, "LATTICEAI_AUTO_READ_CHAT_PATHS", default=False),
194
+ max_local_models=_int(env, "LATTICEAI_MAX_LOCAL_MODELS", 1),
195
+ allow_model_downloads=_bool(env, "LATTICEAI_ALLOW_MODEL_DOWNLOADS", default=False),
196
+ model_download_timeout=_int(env, "LATTICEAI_MODEL_DOWNLOAD_TIMEOUT", 300),
187
197
  embedding_provider=_value(env, "LATTICEAI_EMBEDDING_PROVIDER", "hash").strip().lower(),
188
198
  embedding_profile=_value(env, "LATTICEAI_EMBEDDING_PROFILE", "").strip().lower(),
189
199
  embedding_model=_value(env, "LATTICEAI_EMBEDDING_MODEL", ""),
@@ -0,0 +1,174 @@
1
+ """Managed legacy compatibility surface for legacy import shims.
2
+
3
+ Compatibility modules are intentionally still present for old scripts, VS Code
4
+ extension paths, historical integrations, and pre-graph-package imports. 8.3.0
5
+ stops treating them as vague technical debt: every tracked shim has an owner,
6
+ migration target, removal phase, and replacement import that can be surfaced in
7
+ docs and tests.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Any, Dict, List
15
+
16
+
17
+ LEGACY_COMPATIBILITY_VERSION = "8.3.0"
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class LegacyShim:
22
+ path: str
23
+ owner: str
24
+ replacement: str
25
+ reason: str
26
+ removal_phase: str
27
+ layer: str = "root"
28
+ status: str = "managed"
29
+
30
+ def as_dict(self) -> Dict[str, str]:
31
+ return {
32
+ "path": self.path,
33
+ "owner": self.owner,
34
+ "replacement": self.replacement,
35
+ "reason": self.reason,
36
+ "removal_phase": self.removal_phase,
37
+ "layer": self.layer,
38
+ "status": self.status,
39
+ }
40
+
41
+
42
+ LEGACY_SHIMS: List[LegacyShim] = [
43
+ LegacyShim(
44
+ path="knowledge_graph.py",
45
+ owner="lattice_brain.graph",
46
+ replacement="from lattice_brain.store import KnowledgeGraphStore",
47
+ reason="Historical scripts imported the graph store from the repo root.",
48
+ removal_phase="major-release-after-8.x",
49
+ ),
50
+ LegacyShim(
51
+ path="knowledge_graph_api.py",
52
+ owner="latticeai.api.knowledge_graph",
53
+ replacement="from latticeai.api.knowledge_graph import create_knowledge_graph_router",
54
+ reason="Older API composition roots imported the knowledge graph router from the repo root.",
55
+ removal_phase="major-release-after-8.x",
56
+ ),
57
+ LegacyShim(
58
+ path="kg_schema.py",
59
+ owner="lattice_brain.graph.schema",
60
+ replacement="from lattice_brain.schema import ...",
61
+ reason="Historical graph schema tests and tools referenced root schema symbols.",
62
+ removal_phase="major-release-after-8.x",
63
+ ),
64
+ LegacyShim(
65
+ path="ltcai_cli.py",
66
+ owner="latticeai.cli.entrypoint",
67
+ replacement="from latticeai.cli.entrypoint import main",
68
+ reason="Console entry points and older package installs imported the CLI root module.",
69
+ removal_phase="major-release-after-8.x",
70
+ ),
71
+ LegacyShim(
72
+ path="telegram_bot.py",
73
+ owner="latticeai.integrations.telegram_bot",
74
+ replacement="from latticeai.integrations.telegram_bot import run_bot",
75
+ reason="Existing Telegram automation setups still import the legacy root module.",
76
+ removal_phase="major-release-after-8.x",
77
+ ),
78
+ LegacyShim(
79
+ path="p_reinforce.py",
80
+ owner="latticeai.services.p_reinforce",
81
+ replacement="from latticeai.services.p_reinforce import PReinforceGardener",
82
+ reason="Old gardener scripts referenced the root reinforcement helper.",
83
+ removal_phase="major-release-after-8.x",
84
+ ),
85
+ LegacyShim(
86
+ path="server.py",
87
+ owner="latticeai.server_app",
88
+ replacement="from latticeai.server_app import app, main",
89
+ reason="Deployment docs and local launch scripts historically targeted server.py.",
90
+ removal_phase="major-release-after-8.x",
91
+ ),
92
+ LegacyShim(
93
+ path="local_knowledge_api.py",
94
+ owner="latticeai.api.local_files",
95
+ replacement="from latticeai.api.local_files import create_local_files_router",
96
+ reason="Local folder ingestion integrations used the root local knowledge API.",
97
+ removal_phase="requires-api-route-migration",
98
+ ),
99
+ LegacyShim(
100
+ path="lattice_brain/store.py",
101
+ owner="lattice_brain.graph.store",
102
+ replacement="from lattice_brain.graph.store import KnowledgeGraphStore",
103
+ reason="Pre-graph-package imports used the flat Brain store module.",
104
+ removal_phase="major-release-after-8.x",
105
+ layer="brain-flat",
106
+ ),
107
+ LegacyShim(
108
+ path="lattice_brain/ingest.py",
109
+ owner="lattice_brain.graph.ingest",
110
+ replacement="from lattice_brain.graph.ingest import KnowledgeGraphIngestMixin",
111
+ reason="Pre-graph-package imports used the flat Brain ingest module.",
112
+ removal_phase="major-release-after-8.x",
113
+ layer="brain-flat",
114
+ ),
115
+ LegacyShim(
116
+ path="lattice_brain/retrieval.py",
117
+ owner="lattice_brain.graph.retrieval",
118
+ replacement="from lattice_brain.graph.retrieval import KnowledgeGraphRetrievalMixin",
119
+ reason="Pre-graph-package imports used the flat Brain retrieval module.",
120
+ removal_phase="major-release-after-8.x",
121
+ layer="brain-flat",
122
+ ),
123
+ LegacyShim(
124
+ path="latticeai/brain/store.py",
125
+ owner="lattice_brain.graph.store",
126
+ replacement="from lattice_brain.graph.store import KnowledgeGraphStore",
127
+ reason="The deprecated latticeai.brain namespace remains for package users.",
128
+ removal_phase="major-release-after-8.x",
129
+ layer="deprecated-namespace",
130
+ ),
131
+ LegacyShim(
132
+ path="latticeai/brain/ingest.py",
133
+ owner="lattice_brain.graph.ingest",
134
+ replacement="from lattice_brain.graph.ingest import KnowledgeGraphIngestMixin",
135
+ reason="The deprecated latticeai.brain namespace remains for package users.",
136
+ removal_phase="major-release-after-8.x",
137
+ layer="deprecated-namespace",
138
+ ),
139
+ LegacyShim(
140
+ path="latticeai/services/agent_runtime.py",
141
+ owner="lattice_brain.runtime.agent_runtime",
142
+ replacement="from lattice_brain.runtime.agent_runtime import AgentRuntime",
143
+ reason="Service-layer imports existed before AgentRuntime moved into Brain runtime.",
144
+ removal_phase="major-release-after-8.x",
145
+ layer="service-alias",
146
+ ),
147
+ ]
148
+
149
+
150
+ def legacy_shim_report(root: Path | None = None) -> Dict[str, Any]:
151
+ if root is None:
152
+ root = Path(__file__).resolve().parents[2]
153
+ entries = [shim.as_dict() for shim in LEGACY_SHIMS]
154
+ missing = [shim.path for shim in LEGACY_SHIMS if not (root / shim.path).exists()]
155
+ phases = sorted({shim.removal_phase for shim in LEGACY_SHIMS})
156
+ layers = sorted({shim.layer for shim in LEGACY_SHIMS})
157
+ return {
158
+ "schema_version": "legacy-compatibility/v1",
159
+ "version_target": LEGACY_COMPATIBILITY_VERSION,
160
+ "status": "managed" if not missing else "incomplete",
161
+ "remaining_count": len(LEGACY_SHIMS),
162
+ "missing": missing,
163
+ "removal_phases": phases,
164
+ "layers": layers,
165
+ "shims": entries,
166
+ }
167
+
168
+
169
+ __all__ = [
170
+ "LEGACY_COMPATIBILITY_VERSION",
171
+ "LEGACY_SHIMS",
172
+ "LegacyShim",
173
+ "legacy_shim_report",
174
+ ]
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "8.1.0"
14
+ MARKETPLACE_VERSION = "8.3.0"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -9,6 +9,16 @@ effects at import time.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ __all__ = [
13
+ "ToolPolicy",
14
+ "ToolPermission",
15
+ "TOOL_CATALOG_BRIEF",
16
+ "FILE_CREATE_ACTIONS",
17
+ "LOCAL_WRITE_BLOCKED_PREFIXES",
18
+ "RISK_LEVEL_MAP",
19
+ "ToolRegistry",
20
+ ]
21
+
12
22
  from dataclasses import dataclass, field
13
23
  from typing import Any, Callable, Dict, Mapping, Optional, TypedDict
14
24