ltcai 11.2.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 (247) hide show
  1. package/README.md +46 -53
  2. package/docs/CHANGELOG.md +61 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/MULTI_AGENT_RUNTIME.md +1 -1
  6. package/docs/ONBOARDING.md +1 -1
  7. package/docs/OPERATIONS.md +6 -2
  8. package/docs/PERMISSION_MODE.md +1 -1
  9. package/docs/TRUST_MODEL.md +1 -1
  10. package/docs/WHY_LATTICE.md +1 -1
  11. package/docs/kg-schema.md +2 -2
  12. package/docs/v11.3.0_PLAN.md +202 -0
  13. package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +176 -0
  14. package/lattice_brain/__init__.py +1 -1
  15. package/lattice_brain/graph/_kg_common/__init__.py +287 -0
  16. package/lattice_brain/graph/_kg_common/extraction.py +516 -0
  17. package/lattice_brain/graph/_kg_common/relations.py +161 -0
  18. package/lattice_brain/graph/_kg_common/text.py +479 -0
  19. package/lattice_brain/graph/discovery_index/__init__.py +35 -0
  20. package/lattice_brain/graph/discovery_index/cleanup.py +182 -0
  21. package/lattice_brain/graph/discovery_index/extract.py +137 -0
  22. package/lattice_brain/graph/discovery_index/scan.py +411 -0
  23. package/lattice_brain/graph/discovery_index/upsert.py +495 -0
  24. package/lattice_brain/graph/projection/__init__.py +42 -0
  25. package/lattice_brain/graph/projection/curation.py +500 -0
  26. package/lattice_brain/graph/{projection.py → projection/v2_schema.py} +15 -477
  27. package/lattice_brain/graph/retrieval/__init__.py +54 -0
  28. package/lattice_brain/graph/retrieval/context.py +197 -0
  29. package/lattice_brain/graph/retrieval/graph_view.py +319 -0
  30. package/lattice_brain/graph/retrieval/hybrid.py +488 -0
  31. package/lattice_brain/graph/retrieval/maintenance.py +121 -0
  32. package/lattice_brain/graph/retrieval/signals.py +95 -0
  33. package/lattice_brain/graph/retrieval_vector/__init__.py +42 -0
  34. package/lattice_brain/graph/retrieval_vector/fingerprint.py +97 -0
  35. package/lattice_brain/graph/retrieval_vector/indexing.py +347 -0
  36. package/lattice_brain/graph/retrieval_vector/search.py +560 -0
  37. package/lattice_brain/graph/retrieval_vector/status.py +374 -0
  38. package/lattice_brain/ingestion/__init__.py +130 -0
  39. package/lattice_brain/ingestion/_contract.py +90 -0
  40. package/lattice_brain/ingestion/constants.py +127 -0
  41. package/lattice_brain/ingestion/folder_scan.py +57 -0
  42. package/lattice_brain/ingestion/folders.py +258 -0
  43. package/lattice_brain/ingestion/hashing.py +26 -0
  44. package/lattice_brain/ingestion/jobs_api.py +107 -0
  45. package/lattice_brain/ingestion/models.py +80 -0
  46. package/lattice_brain/ingestion/pipeline.py +486 -0
  47. package/lattice_brain/ingestion/quality.py +209 -0
  48. package/lattice_brain/ingestion/routing.py +295 -0
  49. package/lattice_brain/multimodal/__init__.py +164 -0
  50. package/lattice_brain/multimodal/audio.py +77 -0
  51. package/lattice_brain/multimodal/common.py +118 -0
  52. package/lattice_brain/multimodal/images.py +498 -0
  53. package/lattice_brain/multimodal/ports.py +169 -0
  54. package/lattice_brain/multimodal/video.py +410 -0
  55. package/lattice_brain/portability/__init__.py +90 -0
  56. package/lattice_brain/portability/_contract.py +42 -0
  57. package/lattice_brain/portability/backups.py +338 -0
  58. package/lattice_brain/portability/bundles.py +136 -0
  59. package/lattice_brain/portability/constants.py +93 -0
  60. package/lattice_brain/portability/fsops.py +138 -0
  61. package/lattice_brain/portability/service.py +41 -0
  62. package/lattice_brain/{portability.py → portability/sharing.py} +44 -677
  63. package/lattice_brain/runtime/__init__.py +1 -1
  64. package/lattice_brain/runtime/multi_agent.py +1 -1
  65. package/latticeai/__init__.py +1 -1
  66. package/latticeai/api/chronicle.py +63 -0
  67. package/latticeai/core/agent/__init__.py +93 -0
  68. package/latticeai/core/agent/_contract.py +79 -0
  69. package/latticeai/core/agent/context.py +57 -0
  70. package/latticeai/core/agent/deps.py +125 -0
  71. package/latticeai/core/agent/execution.py +622 -0
  72. package/latticeai/core/agent/planning.py +145 -0
  73. package/latticeai/core/agent/recovery.py +157 -0
  74. package/latticeai/core/agent/runtime.py +210 -0
  75. package/latticeai/core/agent/verification.py +231 -0
  76. package/latticeai/core/embedding_providers/__init__.py +151 -0
  77. package/latticeai/core/embedding_providers/base.py +199 -0
  78. package/latticeai/core/embedding_providers/captions.py +162 -0
  79. package/latticeai/core/embedding_providers/profiles.py +126 -0
  80. package/latticeai/core/embedding_providers/text.py +350 -0
  81. package/latticeai/core/embedding_providers/vision.py +352 -0
  82. package/latticeai/core/file_generation/__init__.py +115 -0
  83. package/latticeai/core/file_generation/bundles.py +76 -0
  84. package/latticeai/core/file_generation/extraction.py +154 -0
  85. package/latticeai/core/file_generation/inference.py +235 -0
  86. package/latticeai/core/file_generation/orchestration.py +152 -0
  87. package/latticeai/core/file_generation/prompting.py +117 -0
  88. package/latticeai/core/file_generation/repair.py +114 -0
  89. package/latticeai/core/file_generation/sanitize.py +61 -0
  90. package/latticeai/core/file_generation/validation.py +201 -0
  91. package/latticeai/core/legacy_compatibility.py +1 -1
  92. package/latticeai/core/marketplace.py +1 -1
  93. package/latticeai/core/messages.py +9 -0
  94. package/latticeai/core/workspace_os_constants.py +1 -1
  95. package/latticeai/integrations/telegram_bot/__init__.py +123 -0
  96. package/latticeai/integrations/telegram_bot/__main__.py +17 -0
  97. package/latticeai/integrations/telegram_bot/config.py +86 -0
  98. package/latticeai/integrations/telegram_bot/dispatch.py +311 -0
  99. package/latticeai/integrations/telegram_bot/flows.py +478 -0
  100. package/latticeai/integrations/telegram_bot/helpers.py +322 -0
  101. package/latticeai/integrations/telegram_bot/screens.py +394 -0
  102. package/latticeai/models/router/__init__.py +88 -0
  103. package/latticeai/models/router/_contract.py +66 -0
  104. package/latticeai/models/router/branding.py +56 -0
  105. package/latticeai/models/router/catalog.py +69 -0
  106. package/latticeai/models/router/documents.py +199 -0
  107. package/latticeai/models/router/errors.py +37 -0
  108. package/latticeai/models/router/generation.py +258 -0
  109. package/latticeai/models/router/loading.py +291 -0
  110. package/latticeai/models/router/local_models.py +85 -0
  111. package/latticeai/models/router/registry.py +147 -0
  112. package/latticeai/runtime/build_phases/__init__.py +82 -0
  113. package/latticeai/runtime/build_phases/features.py +407 -0
  114. package/latticeai/runtime/build_phases/foundation.py +555 -0
  115. package/latticeai/runtime/build_phases/web.py +492 -0
  116. package/latticeai/runtime/runtime_context.py +1 -0
  117. package/latticeai/services/architecture_readiness.py +48 -19
  118. package/latticeai/services/brain_intelligence/__init__.py +58 -0
  119. package/latticeai/services/brain_intelligence/_contract.py +71 -0
  120. package/latticeai/services/brain_intelligence/consistency.py +193 -0
  121. package/latticeai/services/brain_intelligence/constants.py +47 -0
  122. package/latticeai/services/brain_intelligence/digest.py +258 -0
  123. package/latticeai/services/brain_intelligence/health.py +331 -0
  124. package/latticeai/services/brain_intelligence/proposals.py +264 -0
  125. package/latticeai/services/brain_intelligence/sampling.py +84 -0
  126. package/latticeai/services/brain_intelligence/service.py +48 -0
  127. package/latticeai/services/chronicle.py +557 -0
  128. package/latticeai/services/memory_service/__init__.py +52 -0
  129. package/latticeai/services/memory_service/_contract.py +100 -0
  130. package/latticeai/services/memory_service/brief.py +431 -0
  131. package/latticeai/services/memory_service/constants.py +57 -0
  132. package/latticeai/services/memory_service/maintenance.py +138 -0
  133. package/latticeai/services/memory_service/manager.py +186 -0
  134. package/latticeai/services/memory_service/proof.py +136 -0
  135. package/latticeai/services/memory_service/recall.py +225 -0
  136. package/latticeai/services/memory_service/service.py +48 -0
  137. package/latticeai/services/memory_service/stores.py +110 -0
  138. package/latticeai/services/model_runtime/__init__.py +322 -0
  139. package/latticeai/services/model_runtime/cloud.py +87 -0
  140. package/latticeai/services/model_runtime/download.py +282 -0
  141. package/latticeai/services/model_runtime/engines.py +341 -0
  142. package/latticeai/services/model_runtime/loading.py +178 -0
  143. package/latticeai/services/model_runtime/service.py +129 -0
  144. package/latticeai/services/model_runtime/state.py +131 -0
  145. package/latticeai/services/model_runtime/status.py +255 -0
  146. package/latticeai/services/product_readiness.py +15 -7
  147. package/latticeai/setup/wizard/__init__.py +126 -0
  148. package/latticeai/setup/wizard/catalog.py +172 -0
  149. package/latticeai/setup/wizard/detect.py +323 -0
  150. package/latticeai/setup/wizard/install.py +348 -0
  151. package/latticeai/setup/wizard/paths.py +168 -0
  152. package/latticeai/setup/wizard/plans.py +74 -0
  153. package/latticeai/setup/wizard/recommend.py +320 -0
  154. package/package.json +6 -2
  155. package/scripts/bump_version.py +14 -0
  156. package/scripts/capture_release_evidence.mjs +33 -21
  157. package/scripts/check_current_release_docs.mjs +1 -1
  158. package/scripts/check_i18n_namespace_coverage.mjs +41 -4
  159. package/scripts/check_max_file_lines.mjs +102 -0
  160. package/scripts/check_release_evidence_bound.mjs +30 -15
  161. package/scripts/check_screenshot_pixel_delta.py +34 -4
  162. package/scripts/check_server_i18n.mjs +1 -0
  163. package/scripts/generate_rust_parity_fixtures.py +562 -0
  164. package/scripts/lib/mock_server_fingerprint.mjs +94 -0
  165. package/scripts/release_screen_claims.json +31 -2
  166. package/src-tauri/Cargo.lock +361 -3
  167. package/src-tauri/Cargo.toml +6 -1
  168. package/src-tauri/src/backend.rs +349 -0
  169. package/src-tauri/src/folder.rs +33 -0
  170. package/src-tauri/src/main.rs +97 -399
  171. package/src-tauri/tauri.conf.json +1 -1
  172. package/static/app/asset-manifest.json +41 -37
  173. package/static/app/assets/Act-yYpYnn0v.js +1 -0
  174. package/static/app/assets/AdminConsole-DL3Cr5pL.js +1 -0
  175. package/static/app/assets/{Brain-tuhI4sOC.js → Brain-C1HBN0Wf.js} +2 -2
  176. package/static/app/assets/BrainHome-DoXRhUUC.js +2 -0
  177. package/static/app/assets/BrainSignals-6yR6ir5t.js +1 -0
  178. package/static/app/assets/Capture-CFIRsFNE.js +1 -0
  179. package/static/app/assets/Chronicle-BZbEgiwN.js +1 -0
  180. package/static/app/assets/CommandPalette-D2pMxC2I.js +1 -0
  181. package/static/app/assets/Library-DwO3yZST.js +1 -0
  182. package/static/app/assets/{LivingBrain-DBwhto14.js → LivingBrain-Jn1GK0-S.js} +1 -1
  183. package/static/app/assets/ProductFlow-B-w1R4Oo.js +1 -0
  184. package/static/app/assets/ReviewCard-6B27X8Vg.js +3 -0
  185. package/static/app/assets/System-DW8F-2xL.js +1 -0
  186. package/static/app/assets/arrow-left-DXvKg9U6.js +1 -0
  187. package/static/app/assets/{bot-Cia42c2h.js → bot-IM_E_Y12.js} +1 -1
  188. package/static/app/assets/brain-Ci1CkWjM.js +1 -0
  189. package/static/app/assets/{button-2j2Ijzgq.js → button-COwyqfHM.js} +1 -1
  190. package/static/app/assets/circle-check-DfInj-qD.js +1 -0
  191. package/static/app/assets/{circle-pause-BEFeWpVW.js → circle-pause-DEM4A1Y5.js} +1 -1
  192. package/static/app/assets/{circle-play-ujXMcHxl.js → circle-play-C9djDuLd.js} +1 -1
  193. package/static/app/assets/{cpu-k4awryFq.js → cpu-DFdo1gw-.js} +1 -1
  194. package/static/app/assets/{download-DFbLJ_ig.js → download-SnJL6oqk.js} +1 -1
  195. package/static/app/assets/{folder-open-7y_b6xkM.js → folder-open-CqZeDkjE.js} +1 -1
  196. package/static/app/assets/{hard-drive-Bidh02Kr.js → hard-drive-j1jJXYYf.js} +1 -1
  197. package/static/app/assets/{index-DwDl9-8Y.css → index-BLPb5lmE.css} +1 -1
  198. package/static/app/assets/index-_u5iUHDr.js +10 -0
  199. package/static/app/assets/input-B0lPdRQZ.js +1 -0
  200. package/static/app/assets/link-2-CoFbooHS.js +1 -0
  201. package/static/app/assets/{permissionCopy-Bpb83Hx9.js → permissionCopy-BsyLxtao.js} +1 -1
  202. package/static/app/assets/primitives-DEbN-d6p.js +1 -0
  203. package/static/app/assets/search-BybIWPNd.js +1 -0
  204. package/static/app/assets/{share-2-BH1M-WNi.js → share-2-CVtZ_ewX.js} +1 -1
  205. package/static/app/assets/{shield-alert-BlKdBXcG.js → shield-alert-CBi2GNWM.js} +1 -1
  206. package/static/app/assets/{textarea-CCWbUfFB.js → textarea-DNMpB5ih.js} +1 -1
  207. package/static/app/assets/{useFocusTrap-YdHQ7pJ1.js → useFocusTrap-C83t3GXF.js} +1 -1
  208. package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
  209. package/static/app/assets/{useQuery-CXQiwbVT.js → useQuery-Dcp1OChy.js} +1 -1
  210. package/static/app/assets/utils-BlZr7Pd4.js +4 -0
  211. package/static/app/assets/workspace-jJY4RuAV.js +1 -0
  212. package/static/app/index.html +4 -4
  213. package/static/sw.js +1 -1
  214. package/lattice_brain/graph/_kg_common.py +0 -1331
  215. package/lattice_brain/graph/discovery_index.py +0 -1141
  216. package/lattice_brain/graph/retrieval.py +0 -1120
  217. package/lattice_brain/graph/retrieval_vector.py +0 -1293
  218. package/lattice_brain/ingestion.py +0 -1525
  219. package/lattice_brain/multimodal.py +0 -1258
  220. package/latticeai/core/agent.py +0 -1465
  221. package/latticeai/core/embedding_providers.py +0 -1196
  222. package/latticeai/core/file_generation.py +0 -1047
  223. package/latticeai/integrations/telegram_bot.py +0 -1390
  224. package/latticeai/models/router.py +0 -1007
  225. package/latticeai/runtime/build_phases.py +0 -1450
  226. package/latticeai/services/brain_intelligence.py +0 -1083
  227. package/latticeai/services/memory_service.py +0 -1177
  228. package/latticeai/services/model_runtime.py +0 -1281
  229. package/latticeai/setup/wizard.py +0 -1310
  230. package/static/app/assets/Act-AWf0SAKp.js +0 -1
  231. package/static/app/assets/AdminConsole-D0u8Tiyj.js +0 -1
  232. package/static/app/assets/BrainHome-Ts7G_Ila.js +0 -2
  233. package/static/app/assets/BrainSignals-jMYgQ2Ar.js +0 -1
  234. package/static/app/assets/Capture-CqOSzyPr.js +0 -1
  235. package/static/app/assets/CommandPalette-DC0Bzh-I.js +0 -1
  236. package/static/app/assets/Library-CX-bbhmK.js +0 -1
  237. package/static/app/assets/ProductFlow-BHA2cfKI.js +0 -1
  238. package/static/app/assets/ReviewCard-BUhCKRNM.js +0 -3
  239. package/static/app/assets/System-Bu2t5hn1.js +0 -1
  240. package/static/app/assets/arrow-left-Dzwa5zRb.js +0 -1
  241. package/static/app/assets/brain-DJMoqrwx.js +0 -1
  242. package/static/app/assets/index-BpYkzcVm.js +0 -10
  243. package/static/app/assets/input-DSlJJxRs.js +0 -1
  244. package/static/app/assets/primitives-BCx6TvfG.js +0 -1
  245. package/static/app/assets/search-Cgy8cCFJ.js +0 -1
  246. package/static/app/assets/utils-zqPZJxdx.js +0 -4
  247. package/static/app/assets/workspace-DXTihhfU.js +0 -1
@@ -1,1450 +0,0 @@
1
- """The ordered phases that build the Lattice AI application.
2
-
3
- Each phase reads what earlier phases published on the :class:`RuntimeContext`
4
- and publishes its own results. The order below *is* the dependency order and is
5
- fixed by ``tests/unit/test_runtime_context.py``:
6
-
7
- 1. ``platform`` — MLX/GPU device selection (the only step that touches hardware)
8
- 2. ``config`` — configuration, security settings, paths, filesystem layout
9
- 3. ``identity`` — users, sessions, audit, access control, API keys, SSO/VPC config
10
- 4. ``brain`` — embedder, knowledge graph, conversations, hooks, persistence, history
11
- 5. ``domain`` — model router, garden, chat service (needed by the web phase)
12
- 6. ``web`` — lifespan, the FastAPI app, model runtime, static + foundation routers
13
- 7. ``services`` — retrieval/context, chat agent runtime, the typed AppContext
14
- 8. ``foundation_routes`` — mount the foundation routers now that AppContext exists
15
- 9. ``platform_features`` — workspace platform, automation, review, command centre
16
- 10. ``interaction`` — model/chat/search/tools routers and the brain tail routers
17
-
18
- Every heavy import lives *inside* a phase, never at module scope: importing
19
- this module must stay free of GPU init, singleton construction, and filesystem
20
- writes (``tests/unit/test_app_factory.py`` enforces that).
21
-
22
- Why closures still appear here: several handlers must resolve a dependency at
23
- call time rather than at construction time, because the dependency is built by
24
- a later phase. Those read through ``ctx``, which is exactly the late binding
25
- the original single function got from Python's closure rules.
26
- """
27
-
28
- from __future__ import annotations
29
-
30
- import logging
31
- from typing import Any, Dict, List, Optional
32
-
33
- from latticeai.core.quiet import quiet
34
- from latticeai.runtime.runtime_context import RuntimeContext
35
-
36
-
37
- # ── phase 1: platform ────────────────────────────────────────────────────────
38
- def phase_platform(ctx: RuntimeContext) -> None:
39
- """Select the MLX Metal device in the main thread.
40
-
41
- Kept first and alone: it is the one step that talks to hardware, and it
42
- must happen on the main thread before any worker touches MLX.
43
- """
44
- ctx.enter("platform")
45
- try:
46
- import mlx.core as mx
47
-
48
- mx.set_default_device(mx.gpu) # type: ignore[arg-type]
49
- print("✅ MLX Metal context initialized in main thread.")
50
- except Exception as exc:
51
- print(f"⚠️ MLX Metal context unavailable: {exc}")
52
- mx = None # type: ignore[assignment]
53
- ctx.set(mx=mx)
54
-
55
-
56
- # ── phase 2: configuration and paths ─────────────────────────────────────────
57
- def phase_config(ctx: RuntimeContext) -> None:
58
- """Parse configuration once and lay out the data directory."""
59
- ctx.enter("config")
60
-
61
- from latticeai.core.security import host_is_loopback as _host_is_loopback_impl
62
- from latticeai.core.workspace_os import WORKSPACE_OS_VERSION
63
- from latticeai.runtime.config_runtime import build_config_runtime
64
- from latticeai.runtime.security_runtime import build_security_runtime
65
-
66
- try:
67
- import keyring
68
- except Exception:
69
- keyring = None # type: ignore[assignment]
70
-
71
- config_runtime = build_config_runtime(ctx.config_arg)
72
- ctx.set(config_runtime=config_runtime, keyring=keyring)
73
- ctx.adopt(
74
- config_runtime,
75
- "CONFIG",
76
- "APP_MODE",
77
- "IS_PUBLIC_MODE",
78
- "DEFAULT_HOST",
79
- "DEFAULT_PORT",
80
- "ENABLE_TELEGRAM",
81
- "ENABLE_GRAPH",
82
- "AUTOLOAD_MODELS",
83
- "MODEL_IDLE_UNLOAD_SECONDS",
84
- "ALLOW_MODEL_DOWNLOADS",
85
- "MODEL_DOWNLOAD_TIMEOUT",
86
- "ALLOW_LOCAL_MODELS",
87
- "REQUIRE_AUTH",
88
- "ALLOW_PLAINTEXT_API_KEYS",
89
- "CORS_ALLOW_NETWORK",
90
- "CORS_EXTRA_ORIGINS",
91
- "CSRF_TRUSTED_ORIGINS",
92
- "PUBLIC_MODEL",
93
- "LOCAL_MODEL",
94
- "LOCAL_DRAFT_MODEL",
95
- )
96
- ctx.set(
97
- APP_VERSION=WORKSPACE_OS_VERSION,
98
- OPEN_REGISTRATION=ctx.CONFIG.open_registration,
99
- _RATE_LIMIT_ENABLED=ctx.CONFIG.rate_limit_enabled,
100
- # Forwarded headers (X-Forwarded-For / CF-Connecting-IP) are honoured
101
- # for IP rate limiting only when the direct peer is a trusted proxy.
102
- _host_is_loopback=lambda host: _host_is_loopback_impl(host),
103
- )
104
-
105
- security_runtime = build_security_runtime(ctx.CONFIG)
106
- ctx.set(security_runtime=security_runtime)
107
- ctx.adopt(
108
- security_runtime,
109
- "SSO_DISCOVERY_URL",
110
- "SSO_CLIENT_ID",
111
- "SSO_CLIENT_SECRET",
112
- "SSO_REDIRECT_URI",
113
- "SSO_PROVIDER_NAME",
114
- "INVITE_CODE",
115
- "INVITE_COOKIE_SECRET",
116
- "INVITE_GATE_ENABLED",
117
- "SECURE_COOKIES",
118
- )
119
-
120
- from pathlib import Path
121
-
122
- base_dir = Path(__file__).resolve().parent.parent.parent
123
- data_dir = ctx.CONFIG.data_dir
124
- data_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
125
- try:
126
- data_dir.chmod(0o700)
127
- except OSError:
128
- quiet()
129
-
130
- ctx.set(
131
- BASE_DIR=base_dir,
132
- DATA_DIR=data_dir,
133
- STATIC_DIR=ctx.CONFIG.static_dir,
134
- USERS_FILE=data_dir / "users.json",
135
- HISTORY_FILE=data_dir / "chat_history.json",
136
- VPC_FILE=data_dir / "vpc_config.json",
137
- AUDIT_FILE=data_dir / "audit_log.json",
138
- SSO_FILE=data_dir / "sso_config.json",
139
- )
140
-
141
-
142
- # ── phase 3: identity, audit, access ─────────────────────────────────────────
143
- def phase_identity(ctx: RuntimeContext) -> None:
144
- """Users, sessions, audit, access control, API keys, SSO/VPC config."""
145
- ctx.enter("identity")
146
-
147
- from fastapi import HTTPException, Request
148
-
149
- from latticeai.core.mcp_registry import create_mcp_install_state
150
- from latticeai.core.security import (
151
- check_ip_rate_limit as _check_ip_rate_limit,
152
- )
153
- from latticeai.core.security import (
154
- client_ip as _client_ip_impl,
155
- )
156
- from latticeai.core.security import (
157
- enforce_rate_limit as _enforce_rate_limit,
158
- )
159
- from latticeai.core.security import hash_password, verify_password
160
- from latticeai.core.security import (
161
- redact_secret_text as _redact_secret_text,
162
- )
163
- from latticeai.core.users import (
164
- ensure_user_identity,
165
- load_users_file,
166
- migrate_knowledge_graph_identity,
167
- save_users_file,
168
- )
169
- from latticeai.core.users import user_id_for_email as _user_id_for_email
170
- from latticeai.runtime.access_runtime import build_access_runtime
171
- from latticeai.runtime.audit_runtime import build_audit_runtime
172
- from latticeai.runtime.bootstrap import build_session_runtime
173
- from latticeai.runtime.network_config_runtime import build_vpc_runtime
174
- from latticeai.runtime.sso_config_runtime import build_sso_config_runtime
175
- from latticeai.runtime.user_key_runtime import build_user_key_runtime
176
-
177
- # MCP install state (extracted to mcp_registry during the server decomp).
178
- mcp_state = create_mcp_install_state(ctx.DATA_DIR)
179
- ctx.adopt(
180
- mcp_state,
181
- "load_mcp_installs",
182
- "mcp_public_item",
183
- "recommend_mcps",
184
- "install_mcp",
185
- )
186
-
187
- def load_users() -> Dict[str, Any]:
188
- users = load_users_file(ctx.USERS_FILE)
189
- email_to_id = {
190
- email: str(user["id"])
191
- for email, user in users.items()
192
- if isinstance(user, dict) and user.get("id")
193
- }
194
- try:
195
- migrate_knowledge_graph_identity(
196
- ctx.DATA_DIR / "knowledge_graph.sqlite", email_to_id
197
- )
198
- except Exception as exc:
199
- logging.warning("knowledge graph identity migration skipped: %s", exc)
200
- try:
201
- # WORKSPACE_OS is built in the `brain` phase; resolving it here (at
202
- # call time) is why this stays a closure over ctx.
203
- ctx.WORKSPACE_OS.migrate_workspace_identities(email_to_id)
204
- except Exception as exc:
205
- logging.warning("workspace identity migration skipped: %s", exc)
206
- return users
207
-
208
- def save_users(users: Dict[str, Any]) -> None:
209
- save_users_file(ctx.USERS_FILE, users)
210
-
211
- def user_id_for_email(email: Optional[str]) -> Optional[str]:
212
- return _user_id_for_email(load_users(), email)
213
-
214
- def verify_and_migrate_password(
215
- email: str, plain: str, stored: str, users: Dict
216
- ) -> bool:
217
- """평문 비밀번호를 투명하게 해시로 마이그레이션. 마이그레이션 발생 시 audit log 남김."""
218
- if ":" in stored and len(stored) > 64:
219
- return verify_password(plain, stored)
220
- if plain == stored:
221
- users[email]["password"] = hash_password(plain)
222
- save_users(users)
223
- try:
224
- ctx.append_audit_event(
225
- "password_migrated_from_plaintext", user_email=email
226
- )
227
- except Exception as exc:
228
- logging.warning("audit log failed on password migration: %s", exc)
229
- logging.info("Migrated plaintext password to scrypt hash for %s", email)
230
- return True
231
- return False
232
-
233
- def redact_secret_text(text: str) -> str:
234
- return _redact_secret_text(text)
235
-
236
- def _check_rate_limit(
237
- ip: str, action: str, max_calls: int, window_secs: float
238
- ) -> None:
239
- _check_ip_rate_limit(ip, action, max_calls=max_calls, window_secs=window_secs)
240
-
241
- def _client_ip(request: Request) -> str:
242
- return _client_ip_impl(request)
243
-
244
- def enforce_rate_limit(email: str, bucket_key: str) -> None:
245
- _enforce_rate_limit(email, bucket_key, enabled=ctx._RATE_LIMIT_ENABLED)
246
-
247
- ctx.set(
248
- load_users=load_users,
249
- save_users=save_users,
250
- user_id_for_email=user_id_for_email,
251
- verify_and_migrate_password=verify_and_migrate_password,
252
- redact_secret_text=redact_secret_text,
253
- _check_rate_limit=_check_rate_limit,
254
- _client_ip=_client_ip,
255
- enforce_rate_limit=enforce_rate_limit,
256
- )
257
-
258
- # Session token lifecycle; user_id_for_email is the injected subject resolver.
259
- session_runtime = build_session_runtime(user_id_resolver=user_id_for_email)
260
- ctx.adopt(
261
- session_runtime,
262
- "_SESSION_TTL",
263
- "_session_store",
264
- "create_session",
265
- "get_session_email",
266
- "invalidate_session",
267
- )
268
-
269
- # Audit is built after redaction is available, because every event is
270
- # redacted before it is written.
271
- audit_runtime = build_audit_runtime(
272
- audit_file=ctx.AUDIT_FILE,
273
- logging=logging,
274
- redact_fn=redact_secret_text,
275
- )
276
- ctx.adopt(audit_runtime, "get_audit_log", "append_audit_event")
277
-
278
- access_runtime = build_access_runtime(
279
- config=ctx.CONFIG,
280
- require_auth=ctx.REQUIRE_AUTH,
281
- http_exception=HTTPException,
282
- request_type=Request,
283
- load_users=load_users,
284
- get_session_email=ctx.get_session_email,
285
- user_id_for_email=_user_id_for_email,
286
- )
287
- ctx.adopt(
288
- access_runtime,
289
- "get_user_role",
290
- "_extract_bearer_token",
291
- "get_current_user",
292
- "require_user",
293
- "require_admin",
294
- "public_user",
295
- )
296
-
297
- user_key_runtime = build_user_key_runtime(
298
- load_users=load_users,
299
- save_users=save_users,
300
- ensure_user_identity=ensure_user_identity,
301
- keyring=ctx.keyring,
302
- allow_plaintext_api_keys=ctx.ALLOW_PLAINTEXT_API_KEYS,
303
- logging=logging,
304
- http_exception=HTTPException,
305
- )
306
- ctx.adopt(
307
- user_key_runtime, "get_history_user", "get_user_api_key", "set_user_api_key"
308
- )
309
-
310
- # SSO config + OIDC discovery share one closure, so saving the config
311
- # actually invalidates the discovery cache.
312
- sso_runtime = build_sso_config_runtime(
313
- sso_file=ctx.SSO_FILE,
314
- discovery_url=ctx.SSO_DISCOVERY_URL,
315
- client_id=ctx.SSO_CLIENT_ID,
316
- client_secret=ctx.SSO_CLIENT_SECRET,
317
- redirect_uri=ctx.SSO_REDIRECT_URI,
318
- provider_name=ctx.SSO_PROVIDER_NAME,
319
- logging=logging,
320
- )
321
- ctx.adopt(
322
- sso_runtime,
323
- "_sso_env_defaults",
324
- "get_sso_settings",
325
- "public_sso_config",
326
- "save_sso_config",
327
- "_get_sso_discovery",
328
- "_sso_states",
329
- )
330
-
331
- vpc_runtime = build_vpc_runtime(vpc_file=ctx.VPC_FILE, logging=logging)
332
- ctx.adopt(vpc_runtime, "load_vpc_config", "save_vpc_config")
333
-
334
-
335
- # ── phase 4: brain and persistence ───────────────────────────────────────────
336
- def phase_brain(ctx: RuntimeContext) -> None:
337
- """Embedder, knowledge graph, conversations, hooks, persistence, history."""
338
- ctx.enter("brain")
339
-
340
- import os
341
-
342
- from fastapi import HTTPException
343
-
344
- from lattice_brain.graph.schema import set_embed_dim
345
- from lattice_brain.ingestion import IngestionItem
346
- from lattice_brain.storage import storage_from_env
347
- from latticeai.core.audit import (
348
- build_admin_audit_report as _build_admin_audit_report,
349
- )
350
- from latticeai.core.audit import (
351
- build_sensitivity_report as _build_sensitivity_report,
352
- )
353
- from latticeai.core.audit import (
354
- classify_sensitive_message as _classify_sensitive_message,
355
- )
356
- from latticeai.core.embedding_providers import (
357
- resolve_embedder,
358
- resolve_embedding_profile,
359
- )
360
- from latticeai.core.security import (
361
- bytes_match_extension as _bytes_match_extension_impl,
362
- )
363
- from latticeai.models.router import normalize_branding
364
- from latticeai.runtime.brain_runtime import build_brain_runtime
365
- from latticeai.runtime.history_runtime import build_history_query_runtime
366
- from latticeai.runtime.history_writer import HistoryWriterDeps, write_chat_turn
367
- from latticeai.runtime.hooks_runtime import build_hooks_runtime
368
- from latticeai.runtime.persistence_runtime import build_persistence_runtime
369
-
370
- # Resolve the configured embedding provider once. Degrades to the offline
371
- # hash fallback when unavailable, recording requested-vs-active provider.
372
- try:
373
- embedding_profile = resolve_embedding_profile(ctx.CONFIG.embedding_profile)
374
- except ValueError as exc:
375
- logging.warning("Embedding profile ignored: %s", exc)
376
- embedding_profile = {}
377
-
378
- provider = ctx.CONFIG.embedding_provider
379
- model = ctx.CONFIG.embedding_model or str(embedding_profile.get("model") or "")
380
- dim = ctx.CONFIG.embedding_dim or int(embedding_profile.get("dimensions") or 0)
381
- if ctx.CONFIG.embedding_profile and provider in {"", "hash", "local", "fallback"}:
382
- provider = str(embedding_profile.get("provider") or provider)
383
-
384
- embedder = resolve_embedder(
385
- provider,
386
- model=model,
387
- base_url=ctx.CONFIG.embedding_base_url,
388
- api_key=ctx.CONFIG.embedding_api_key,
389
- dim=dim,
390
- timeout=ctx.CONFIG.embedding_timeout,
391
- extra={"target": ctx.CONFIG.embedding_custom_target},
392
- probe=provider not in {"", "hash", "local", "fallback"},
393
- )
394
- set_embed_dim(int(getattr(embedder, "dim", None) or dim or 384))
395
- if embedder.fell_back:
396
- logging.warning(
397
- "Embedding provider %s unavailable: %s", embedder.requested, embedder.detail
398
- )
399
-
400
- storage_engine = (
401
- storage_from_env(os.environ, data_dir=ctx.DATA_DIR) if ctx.ENABLE_GRAPH else None
402
- )
403
- ctx.set(
404
- EMBEDDING_PROFILE=embedding_profile,
405
- EMBEDDER=embedder,
406
- STORAGE_ENGINE=storage_engine,
407
- )
408
-
409
- brain_runtime = build_brain_runtime(
410
- data_dir=ctx.DATA_DIR,
411
- history_file=ctx.HISTORY_FILE,
412
- enable_graph=ctx.ENABLE_GRAPH,
413
- embedder=embedder,
414
- storage_engine=storage_engine,
415
- )
416
- ctx.set(brain_runtime=brain_runtime)
417
- # CONVERSATIONS is the v4 durable episodic store: unbounded history in the
418
- # same SQLite file as the graph, so backup/restore covers it for free.
419
- ctx.adopt(brain_runtime, "KNOWLEDGE_GRAPH", "CONVERSATIONS")
420
-
421
- def save_to_history(
422
- role: str,
423
- message: str,
424
- user_email: Optional[str] = None,
425
- user_nickname: Optional[str] = None,
426
- source: Optional[str] = None,
427
- conversation_id: Optional[str] = None,
428
- workspace_id: Optional[str] = None,
429
- ) -> None:
430
- """Persist one chat turn. Logic lives in runtime/history_writer.py.
431
-
432
- The dependency bundle is built per call because INGESTION_PIPELINE is
433
- published later in this same phase; resolving through ctx keeps the
434
- ordering contract in one place instead of reordering the phase.
435
- """
436
- write_chat_turn(
437
- role,
438
- message,
439
- user_email=user_email,
440
- user_nickname=user_nickname,
441
- source=source,
442
- conversation_id=conversation_id,
443
- workspace_id=workspace_id,
444
- deps=HistoryWriterDeps(
445
- conversations=ctx.CONVERSATIONS,
446
- append_audit_event=ctx.append_audit_event,
447
- classify_sensitive_message=ctx.classify_sensitive_message,
448
- redact_secret_text=ctx.redact_secret_text,
449
- normalize_branding=normalize_branding,
450
- ingestion_pipeline=ctx.INGESTION_PIPELINE,
451
- ingestion_item_factory=IngestionItem,
452
- enable_graph=ctx.ENABLE_GRAPH,
453
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
454
- ),
455
- )
456
-
457
- def classify_sensitive_message(item: Dict, index: int) -> Dict:
458
- return _classify_sensitive_message(item, index)
459
-
460
- def build_sensitivity_report(history: List[Dict]) -> Dict:
461
- return _build_sensitivity_report(history)
462
-
463
- def build_admin_audit_report(
464
- users: Dict, audit_events: Optional[List[Dict]] = None
465
- ) -> Dict:
466
- graph_stats = None
467
- try:
468
- if ctx.ENABLE_GRAPH and ctx.KNOWLEDGE_GRAPH:
469
- graph_stats = ctx.KNOWLEDGE_GRAPH.stats()
470
- except Exception:
471
- quiet()
472
- return _build_admin_audit_report(
473
- ctx.AUDIT_FILE,
474
- users,
475
- get_user_role=ctx.get_user_role,
476
- graph_stats=graph_stats,
477
- audit_events=audit_events,
478
- )
479
-
480
- def _bytes_match_extension(data: bytes, ext: str) -> bool:
481
- return _bytes_match_extension_impl(data, ext)
482
-
483
- def _require_graph() -> None:
484
- if not ctx.ENABLE_GRAPH or ctx.KNOWLEDGE_GRAPH is None:
485
- raise HTTPException(
486
- status_code=404,
487
- detail=(
488
- "지식 그래프가 비활성화되어 있습니다. "
489
- "LATTICEAI_ENABLE_GRAPH=true 설정 후 다시 시도해 주세요."
490
- ),
491
- )
492
-
493
- def _workspace_graph() -> Any:
494
- return (
495
- ctx.KNOWLEDGE_GRAPH
496
- if (ctx.ENABLE_GRAPH and ctx.KNOWLEDGE_GRAPH)
497
- else None
498
- )
499
-
500
- ctx.set(
501
- save_to_history=save_to_history,
502
- classify_sensitive_message=classify_sensitive_message,
503
- build_sensitivity_report=build_sensitivity_report,
504
- build_admin_audit_report=build_admin_audit_report,
505
- _bytes_match_extension=_bytes_match_extension,
506
- _require_graph=_require_graph,
507
- _workspace_graph=_workspace_graph,
508
- )
509
-
510
- # The hooks registry is built ahead of the watcher so folder-watch
511
- # reindexes can fire the pre_index/post_index lifecycle hooks.
512
- hooks_runtime = build_hooks_runtime(
513
- data_dir=ctx.DATA_DIR,
514
- enable_graph=ctx.ENABLE_GRAPH,
515
- knowledge_graph_getter=lambda: ctx.KNOWLEDGE_GRAPH,
516
- )
517
- ctx.adopt(hooks_runtime, "HOOKS_REGISTRY", "LOCAL_KG_WATCHER")
518
-
519
- persistence_runtime = build_persistence_runtime(
520
- data_dir=ctx.DATA_DIR,
521
- base_dir=ctx.BASE_DIR,
522
- enable_graph=ctx.ENABLE_GRAPH,
523
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
524
- hooks_registry=ctx.HOOKS_REGISTRY,
525
- history_file=ctx.HISTORY_FILE,
526
- conversations=ctx.CONVERSATIONS,
527
- user_id_for_email=ctx.user_id_for_email,
528
- audit=lambda action, detail, user: ctx.append_audit_event(
529
- action, user_email=user, **detail
530
- ),
531
- )
532
- ctx.adopt(
533
- persistence_runtime,
534
- "REALTIME_BUS",
535
- "WORKSPACE_OS",
536
- "WORKSPACE_SERVICE",
537
- "INVITATION_STORE",
538
- "PLUGIN_REGISTRY",
539
- "TEMPLATE_CATALOG",
540
- "AGENT_REGISTRY",
541
- "MEMORY_SERVICE",
542
- "BRAIN_INTELLIGENCE",
543
- "AUTOMATION_INTELLIGENCE",
544
- "INGESTION_PIPELINE",
545
- "MULTIMODAL_PORTS",
546
- "DEVICE_IDENTITY",
547
- "KG_PORTABILITY",
548
- "FUNNEL_METRICS",
549
- )
550
-
551
- # History reads/clears. The write path (save_to_history above) stays a
552
- # closure because it is bound to redaction/audit/ingestion.
553
- history_query_runtime = build_history_query_runtime(
554
- conversations=ctx.CONVERSATIONS,
555
- workspace_service=ctx.WORKSPACE_SERVICE,
556
- require_auth=ctx.REQUIRE_AUTH,
557
- logging=logging,
558
- )
559
- ctx.adopt(
560
- history_query_runtime,
561
- "_history_allowed_workspaces_for",
562
- "_history_include_legacy_global",
563
- "get_history",
564
- "conversation_title",
565
- "group_history_conversations",
566
- "get_conversation_messages",
567
- "clear_history",
568
- "clear_conversation",
569
- )
570
-
571
-
572
- # ── phase 5: domain singletons (must precede the web app) ────────────────────
573
- def phase_domain(ctx: RuntimeContext) -> None:
574
- """The model router, the garden, and the chat service.
575
-
576
- Separated from :func:`phase_services` because ``phase_web`` needs the model
577
- router: the lifespan hooks, the static status routes, and the model runtime
578
- service are all wired against it.
579
- """
580
- ctx.enter("domain")
581
-
582
- from lattice_brain.graph.runtime import set_llm_router
583
- from latticeai.models.router import LLMRouter
584
- from latticeai.services.chat_service import ChatService
585
- from latticeai.services.p_reinforce import PReinforceGardener
586
- from latticeai.services.tool_dispatch import configure_tool_dispatch
587
-
588
- model_router = LLMRouter()
589
- set_llm_router(model_router)
590
- configure_tool_dispatch(load_users=ctx.load_users, get_user_role=ctx.get_user_role)
591
- ctx.set(model_router=model_router)
592
-
593
- # v4 garden absorption: the vault is the user-owned markdown mirror and the
594
- # brain is authoritative. Existing notes import idempotently (content-hash
595
- # dedup), and garden context queries the brain rather than rescanning.
596
- gardener = PReinforceGardener(
597
- ingestion_pipeline=ctx.INGESTION_PIPELINE if ctx.ENABLE_GRAPH else None,
598
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
599
- )
600
- if ctx.ENABLE_GRAPH:
601
- try:
602
- garden_import = gardener.import_vault()
603
- if garden_import.get("failed"):
604
- logging.warning(
605
- "garden vault import: %s notes failed to ingest",
606
- garden_import["failed"],
607
- )
608
- except Exception as exc:
609
- logging.warning("garden vault import skipped: %s", exc)
610
- ctx.set(gardener=gardener)
611
-
612
- # The chat service owns persistence and trace behaviour once its user-key
613
- # dependencies exist.
614
- ctx.set(
615
- CHAT_SERVICE=ChatService(
616
- store=ctx.WORKSPACE_OS,
617
- get_history=ctx.get_history,
618
- save_to_history=ctx.save_to_history,
619
- get_history_user=ctx.get_history_user,
620
- )
621
- )
622
-
623
-
624
- def self_model_port(workspace_graph: Any) -> Any:
625
- """The agent loop's Self-Model port (v11.2.0).
626
-
627
- 11.1.0 built ``executor_prompt_for(self_model_summary=…)`` and had nothing
628
- to pass it; this is what the composition root passes. ``summary_for_prompt``
629
- never raises and answers ``""`` for a Brain that knows nothing about its
630
- owner — which produces exactly the pre-11.2.0 prompt bytes.
631
-
632
- A module-level factory rather than a closure inside ``phase_services``
633
- because a port that cannot be called on its own cannot be tested on its own.
634
- """
635
-
636
- def _summary(*, user_email: Any = None, workspace_id: Any = None) -> str:
637
- from lattice_brain.self_model import summary_for_prompt
638
-
639
- graph = workspace_graph()
640
- if graph is None:
641
- return ""
642
- return summary_for_prompt(graph, workspace_id=workspace_id)
643
-
644
- return _summary
645
-
646
-
647
- # ── phase 6b: retrieval, agent runtime, and the typed AppContext ─────────────
648
- def phase_services(ctx: RuntimeContext) -> None:
649
- """Retrieval/context assembly, the chat agent runtime, and the AppContext.
650
-
651
- Runs after ``phase_web`` because the AppContext carries handles the web
652
- phase produces (``ui_file_response``, ``local_sysinfo``, ``graph_stats``)
653
- and the telegram mirror needs the lifespan's ``_spawn``.
654
- """
655
- ctx.enter("services")
656
-
657
- from latticeai.api.chat import build_recent_chat_context
658
- from latticeai.core.enterprise import capability_registry
659
- from latticeai.core.mcp_registry import (
660
- SKILLS_DIR,
661
- _fetch_skills_marketplace,
662
- install_skill,
663
- )
664
- from latticeai.core.workspace_os import remove_skill_directory
665
- from latticeai.runtime.chat_wiring import (
666
- build_chat_agent_runtime_from_context,
667
- maybe_build_telegram_chat_mirror,
668
- )
669
- from latticeai.runtime.context_runtime import build_context_runtime
670
- from latticeai.services.app_context import AppContext
671
- from latticeai.services.tool_dispatch import build_agent_runtime
672
- from latticeai.setup.wizard import get_recommendations, scan_environment
673
- from latticeai.tools import execute_tool, knowledge_save
674
-
675
- gardener = ctx.gardener
676
- model_router = ctx.model_router
677
-
678
- def _workspace_settings_payload() -> Dict:
679
- return {
680
- "mode": ctx.APP_MODE,
681
- "host": ctx.DEFAULT_HOST,
682
- "port": ctx.DEFAULT_PORT,
683
- "require_auth": ctx.REQUIRE_AUTH,
684
- "enable_graph": ctx.ENABLE_GRAPH,
685
- "allow_local_models": ctx.ALLOW_LOCAL_MODELS,
686
- "static_dir": str(ctx.STATIC_DIR),
687
- "data_dir": str(ctx.DATA_DIR),
688
- }
689
-
690
- def _workspace_models_payload() -> Dict:
691
- return {
692
- "current_model": ctx.model_router.current_model_id,
693
- "loaded_models": ctx.model_router.loaded_model_ids,
694
- "public_model": ctx.PUBLIC_MODEL,
695
- "local_model": ctx.LOCAL_MODEL,
696
- "local_draft_model": ctx.LOCAL_DRAFT_MODEL,
697
- }
698
-
699
- def _embedding_info() -> dict:
700
- from latticeai.core.embedding_providers import (
701
- PROVIDER_TYPES,
702
- embedding_provider_profiles,
703
- )
704
-
705
- info = ctx.EMBEDDER.as_dict()
706
- info["available_providers"] = list(PROVIDER_TYPES)
707
- info["profile"] = ctx.CONFIG.embedding_profile or ""
708
- info["profiles"] = embedding_provider_profiles()
709
- return info
710
-
711
- def _allowed_workspaces_for(user: Any) -> Any:
712
- # No-auth local mode is single-user: no scoping. With auth, reads are
713
- # scoped to the caller's memberships (legacy-global rows need an
714
- # explicit maintenance-only opt-in).
715
- if not ctx.REQUIRE_AUTH or not user:
716
- return None
717
- return ctx.PLATFORM.allowed_scopes(user)
718
-
719
- def _recent_chat_context(
720
- limit: int = 10,
721
- include_image_missing_replies: bool = True,
722
- user_email: Optional[str] = None,
723
- conversation_id: Optional[str] = None,
724
- workspace_id: Optional[str] = None,
725
- ) -> str:
726
- return build_recent_chat_context(
727
- get_history=ctx.get_history,
728
- limit=limit,
729
- include_image_missing_replies=include_image_missing_replies,
730
- user_email=user_email,
731
- conversation_id=conversation_id,
732
- workspace_id=workspace_id,
733
- )
734
-
735
- ctx.set(
736
- _workspace_settings_payload=_workspace_settings_payload,
737
- _workspace_models_payload=_workspace_models_payload,
738
- _embedding_info=_embedding_info,
739
- _allowed_workspaces_for=_allowed_workspaces_for,
740
- _recent_chat_context=_recent_chat_context,
741
- )
742
-
743
- context_runtime = build_context_runtime(
744
- graph_store=ctx._workspace_graph(),
745
- ingestion_pipeline=ctx.INGESTION_PIPELINE,
746
- memory_service=ctx.MEMORY_SERVICE,
747
- gardener=gardener,
748
- require_auth=ctx.REQUIRE_AUTH,
749
- allowed_scopes_for_user=lambda user_email: ctx.PLATFORM.allowed_scopes(
750
- user_email
751
- ),
752
- )
753
- ctx.adopt(
754
- context_runtime,
755
- "SEARCH_SERVICE",
756
- "BRAIN_MEMORY",
757
- "CONTEXT_ASSEMBLER",
758
- "ARTIFACT_LEDGER",
759
- "_scoped_hybrid_search",
760
- )
761
-
762
- # Telegram chat mirror: registered only when ENABLE_TELEGRAM is truthy, so
763
- # latticeai.api.chat never imports the 45KB telegram module.
764
- ctx.set(
765
- on_chat_message=maybe_build_telegram_chat_mirror(
766
- enable_telegram=ctx.ENABLE_TELEGRAM,
767
- spawn=ctx._spawn,
768
- )
769
- )
770
-
771
- ctx.set(
772
- CHAT_AGENT_RUNTIME=build_chat_agent_runtime_from_context(
773
- build_agent_runtime=build_agent_runtime,
774
- model_router=model_router,
775
- execute_tool=execute_tool,
776
- recent_chat_context=_recent_chat_context,
777
- clear_history=ctx.clear_history,
778
- knowledge_save=knowledge_save,
779
- audit=ctx.append_audit_event,
780
- hooks=ctx.HOOKS_REGISTRY,
781
- brain_memory=ctx.BRAIN_MEMORY,
782
- self_model_summary=self_model_port(
783
- lambda: ctx.KNOWLEDGE_GRAPH if ctx.ENABLE_GRAPH else None
784
- ),
785
- )
786
- )
787
-
788
- # One typed context object replaces the historical 25-30-kwarg wiring.
789
- ctx.set(
790
- app_context=AppContext(
791
- config=ctx.CONFIG,
792
- data_dir=ctx.DATA_DIR,
793
- static_dir=ctx.STATIC_DIR,
794
- base_dir=ctx.BASE_DIR,
795
- skills_dir=SKILLS_DIR,
796
- model_router=model_router,
797
- workspace_store=ctx.WORKSPACE_OS,
798
- workspace_service=ctx.WORKSPACE_SERVICE,
799
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
800
- local_kg_watcher=ctx.LOCAL_KG_WATCHER,
801
- chat_service=ctx.CHAT_SERVICE,
802
- context_assembler=ctx.CONTEXT_ASSEMBLER,
803
- artifact_ledger=ctx.ARTIFACT_LEDGER,
804
- brain_memory=ctx.BRAIN_MEMORY,
805
- ingestion_pipeline=ctx.INGESTION_PIPELINE if ctx.ENABLE_GRAPH else None,
806
- chat_agent_runtime=ctx.CHAT_AGENT_RUNTIME,
807
- gardener=gardener,
808
- hooks=ctx.HOOKS_REGISTRY,
809
- realtime_bus=ctx.REALTIME_BUS,
810
- capability_registry=capability_registry,
811
- require_user=ctx.require_user,
812
- require_admin=ctx.require_admin,
813
- get_current_user=ctx.get_current_user,
814
- load_users=ctx.load_users,
815
- get_user_role=ctx.get_user_role,
816
- enforce_rate_limit=ctx.enforce_rate_limit,
817
- allowed_workspaces_for=ctx._history_allowed_workspaces_for,
818
- append_audit_event=ctx.append_audit_event,
819
- get_audit_log=ctx.get_audit_log,
820
- get_history=ctx.get_history,
821
- get_history_user=ctx.get_history_user,
822
- save_to_history=ctx.save_to_history,
823
- clear_history=ctx.clear_history,
824
- clear_conversation=ctx.clear_conversation,
825
- group_history_conversations=ctx.group_history_conversations,
826
- get_conversation_messages=ctx.get_conversation_messages,
827
- conversation_title=ctx.conversation_title,
828
- enable_graph=ctx.ENABLE_GRAPH,
829
- require_graph=ctx._require_graph,
830
- workspace_graph=ctx._workspace_graph,
831
- graph_stats=ctx._graph_stats_safe,
832
- # Provider, not a value: REVIEW_QUEUE lands two phases later.
833
- review_queue=lambda: ctx.REVIEW_QUEUE,
834
- workspace_models=_workspace_models_payload,
835
- workspace_settings=_workspace_settings_payload,
836
- scan_environment=scan_environment,
837
- local_sysinfo=ctx.local_sysinfo,
838
- get_recommendations=get_recommendations,
839
- fetch_skills_marketplace=_fetch_skills_marketplace,
840
- install_skill=install_skill,
841
- remove_skill_directory=remove_skill_directory,
842
- redact_secret_text=ctx.redact_secret_text,
843
- ui_file_response=ctx.ui_file_response,
844
- public_model=ctx.PUBLIC_MODEL,
845
- local_model=ctx.LOCAL_MODEL or "",
846
- on_chat_message=ctx.on_chat_message,
847
- funnel_metrics=ctx.FUNNEL_METRICS,
848
- )
849
- )
850
- ctx.app.state.context = ctx.app_context
851
-
852
-
853
- # ── phase 6: web app and foundation routers ──────────────────────────────────
854
- def phase_web(ctx: RuntimeContext) -> None:
855
- """Lifespan, the FastAPI app, model runtime service, foundation routers."""
856
- ctx.enter("web")
857
-
858
- from latticeai.api.admin import create_admin_router
859
- from latticeai.api.auth import create_auth_router
860
- from latticeai.api.invitations import create_invitations_router
861
- from latticeai.api.security_dashboard import (
862
- create_security_router as _create_security_router,
863
- )
864
- from latticeai.api.static_routes import create_static_routes_router
865
- from latticeai.core.policy import policy_matrix
866
- from latticeai.core.product_hardening import build_product_hardening_status
867
- from latticeai.core.security import hash_password
868
- from latticeai.core.users import ensure_user_identity
869
- from latticeai.runtime.lifespan_runtime import build_lifespan_runtime
870
- from latticeai.runtime.model_wiring import configure_model_runtime_from_context
871
- from latticeai.runtime.router_registration import (
872
- build_auth_admin_security_router_bundle,
873
- build_static_routes_bundle,
874
- )
875
- from latticeai.runtime.web_runtime import build_web_runtime
876
- from latticeai.services.model_runtime import (
877
- LOCAL_SERVER_PROCESSES,
878
- build_model_runtime,
879
- )
880
- from latticeai.tools import ensure_agent_root
881
-
882
- lifespan_runtime = build_lifespan_runtime(
883
- app_mode=ctx.APP_MODE,
884
- enable_telegram=ctx.ENABLE_TELEGRAM,
885
- autoload_models=ctx.AUTOLOAD_MODELS,
886
- is_public_mode=ctx.IS_PUBLIC_MODE,
887
- public_model=ctx.PUBLIC_MODEL,
888
- allow_local_models=ctx.ALLOW_LOCAL_MODELS,
889
- local_model=ctx.LOCAL_MODEL,
890
- local_draft_model=ctx.LOCAL_DRAFT_MODEL,
891
- model_idle_unload_seconds=ctx.MODEL_IDLE_UNLOAD_SECONDS,
892
- model_router=ctx.model_router,
893
- local_kg_watcher=ctx.LOCAL_KG_WATCHER,
894
- local_server_processes=LOCAL_SERVER_PROCESSES,
895
- logger=logging,
896
- )
897
- ctx.adopt(lifespan_runtime, "_spawn", "lifespan")
898
-
899
- web_runtime = build_web_runtime(
900
- app_mode=ctx.APP_MODE,
901
- app_version=ctx.APP_VERSION,
902
- lifespan=ctx.lifespan,
903
- default_host=ctx.DEFAULT_HOST,
904
- default_port=ctx.DEFAULT_PORT,
905
- cors_extra_origins=ctx.CORS_EXTRA_ORIGINS,
906
- cors_allow_network=ctx.CORS_ALLOW_NETWORK,
907
- static_dir=ctx.STATIC_DIR,
908
- csrf_trusted_origins=ctx.CSRF_TRUSTED_ORIGINS,
909
- )
910
- ctx.adopt(web_runtime, "app")
911
- ensure_agent_root()
912
-
913
- ctx.set(
914
- model_runtime_service=configure_model_runtime_from_context(
915
- build_model_runtime=build_model_runtime,
916
- router=ctx.model_router,
917
- APP_MODE=ctx.APP_MODE,
918
- DEFAULT_HOST=ctx.DEFAULT_HOST,
919
- DEFAULT_PORT=ctx.DEFAULT_PORT,
920
- DATA_DIR=ctx.DATA_DIR,
921
- BASE_DIR=ctx.BASE_DIR,
922
- ENABLE_TELEGRAM=ctx.ENABLE_TELEGRAM,
923
- ENABLE_GRAPH=ctx.ENABLE_GRAPH,
924
- AUTOLOAD_MODELS=ctx.AUTOLOAD_MODELS,
925
- MODEL_IDLE_UNLOAD_SECONDS=ctx.MODEL_IDLE_UNLOAD_SECONDS,
926
- ALLOW_MODEL_DOWNLOADS=ctx.ALLOW_MODEL_DOWNLOADS,
927
- MODEL_DOWNLOAD_TIMEOUT=ctx.MODEL_DOWNLOAD_TIMEOUT,
928
- ALLOW_LOCAL_MODELS=ctx.ALLOW_LOCAL_MODELS,
929
- REQUIRE_AUTH=ctx.REQUIRE_AUTH,
930
- INVITE_GATE_ENABLED=ctx.INVITE_GATE_ENABLED,
931
- ALLOW_PLAINTEXT_API_KEYS=ctx.ALLOW_PLAINTEXT_API_KEYS,
932
- CORS_ALLOW_NETWORK=ctx.CORS_ALLOW_NETWORK,
933
- PUBLIC_MODEL=ctx.PUBLIC_MODEL,
934
- LOCAL_MODEL=ctx.LOCAL_MODEL,
935
- IS_PUBLIC_MODE=ctx.IS_PUBLIC_MODE,
936
- keyring=ctx.keyring,
937
- get_current_user=ctx.get_current_user,
938
- get_user_api_key=ctx.get_user_api_key,
939
- )
940
- )
941
-
942
- static_routes_bundle = build_static_routes_bundle(
943
- create_static_routes_router=create_static_routes_router,
944
- static_dir=ctx.STATIC_DIR,
945
- invite_gate_enabled=ctx.INVITE_GATE_ENABLED,
946
- invite_code=ctx.INVITE_CODE,
947
- invite_cookie_secret=ctx.INVITE_COOKIE_SECRET,
948
- secure_cookies=ctx.SECURE_COOKIES,
949
- app_mode=ctx.APP_MODE,
950
- model_router=ctx.model_router,
951
- require_user=ctx.require_user,
952
- )
953
- ctx.adopt(
954
- static_routes_bundle,
955
- "STATIC_ROUTES",
956
- "ui_file_response",
957
- "local_sysinfo",
958
- "invite_authorized",
959
- )
960
-
961
- foundation_router_bundle = build_auth_admin_security_router_bundle(
962
- create_auth_router=create_auth_router,
963
- load_users=ctx.load_users,
964
- save_users=ctx.save_users,
965
- hash_password=hash_password,
966
- verify_and_migrate_password=ctx.verify_and_migrate_password,
967
- create_session=ctx.create_session,
968
- get_session_email=ctx.get_session_email,
969
- invalidate_session=ctx.invalidate_session,
970
- extract_bearer_token=ctx._extract_bearer_token,
971
- get_user_role=ctx.get_user_role,
972
- require_user=ctx.require_user,
973
- check_ip_rate_limit=ctx._check_rate_limit,
974
- client_ip=ctx._client_ip,
975
- get_sso_settings=ctx.get_sso_settings,
976
- get_sso_discovery=ctx._get_sso_discovery,
977
- public_sso_config=ctx.public_sso_config,
978
- open_registration=ctx.OPEN_REGISTRATION,
979
- session_ttl=ctx._SESSION_TTL,
980
- require_auth=ctx.REQUIRE_AUTH,
981
- secure_cookies=ctx.SECURE_COOKIES,
982
- invite_authorized=ctx.invite_authorized,
983
- ensure_identity=ensure_user_identity,
984
- create_admin_router=create_admin_router,
985
- require_admin=ctx.require_admin,
986
- get_history=ctx.get_history,
987
- get_audit_log=ctx.get_audit_log,
988
- audit_file=ctx.AUDIT_FILE,
989
- public_user=ctx.public_user,
990
- load_vpc_config=ctx.load_vpc_config,
991
- save_vpc_config=ctx.save_vpc_config,
992
- build_admin_audit_report=ctx.build_admin_audit_report,
993
- build_sensitivity_report=ctx.build_sensitivity_report,
994
- append_audit_event=ctx.append_audit_event,
995
- save_sso_config=ctx.save_sso_config,
996
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
997
- enable_graph=ctx.ENABLE_GRAPH,
998
- logger=logging,
999
- invite_code=ctx.INVITE_CODE,
1000
- invite_gate_enabled=ctx.INVITE_GATE_ENABLED,
1001
- default_port=ctx.DEFAULT_PORT,
1002
- policy_matrix=policy_matrix,
1003
- build_product_hardening_status=build_product_hardening_status,
1004
- config=ctx.CONFIG,
1005
- kg_portability=ctx.KG_PORTABILITY,
1006
- device_identity=ctx.DEVICE_IDENTITY,
1007
- create_invitations_router=create_invitations_router,
1008
- invitation_store=ctx.INVITATION_STORE,
1009
- workspace_service=ctx.WORKSPACE_SERVICE,
1010
- user_id_for_email=ctx.user_id_for_email,
1011
- create_security_router=_create_security_router,
1012
- classify_sensitive_message=ctx.classify_sensitive_message,
1013
- )
1014
- ctx.adopt(
1015
- foundation_router_bundle,
1016
- "auth_router",
1017
- "admin_router",
1018
- "invitations_router",
1019
- "security_router",
1020
- "_graph_stats_safe",
1021
- "_product_hardening_status",
1022
- "_security_audit_events_safe",
1023
- "_security_list_uploaded_files",
1024
- )
1025
-
1026
-
1027
- def phase_foundation_routes(ctx: RuntimeContext) -> None:
1028
- """Mount the foundation routers once the AppContext exists."""
1029
- ctx.enter("foundation_routes")
1030
-
1031
- from latticeai.api.workspace import create_workspace_router
1032
- from latticeai.runtime.router_registration import register_foundation_routers
1033
-
1034
- register_foundation_routers(
1035
- ctx.app,
1036
- static_router=ctx.STATIC_ROUTES.router,
1037
- auth_router=ctx.auth_router,
1038
- admin_router=ctx.admin_router,
1039
- invitations_router=ctx.invitations_router,
1040
- security_router=ctx.security_router,
1041
- create_workspace_router=create_workspace_router,
1042
- context=ctx.app_context,
1043
- )
1044
-
1045
-
1046
- # ── phase 7: platform features ───────────────────────────────────────────────
1047
- def phase_platform_features(ctx: RuntimeContext) -> None:
1048
- """Workspace platform, automation, review queue, command centre, proposals."""
1049
- ctx.enter("platform_features")
1050
-
1051
- from latticeai.api.agents import create_agents_router
1052
- from latticeai.api.automation_intelligence import (
1053
- create_automation_intelligence_router,
1054
- )
1055
- from latticeai.api.change_proposals import create_change_proposals_router
1056
- from latticeai.api.command_center import create_command_center_router
1057
- from latticeai.api.evidence_actions import create_evidence_actions_router
1058
- from latticeai.api.funnel_metrics import create_funnel_metrics_router
1059
- from latticeai.api.marketplace import create_marketplace_router
1060
- from latticeai.api.plugins import create_plugins_router
1061
- from latticeai.api.project_sessions import create_project_sessions_router
1062
- from latticeai.api.realtime import create_realtime_router
1063
- from latticeai.api.voice_capture import create_voice_capture_router
1064
- from latticeai.api.workflow_designer import create_workflow_designer_router
1065
- from latticeai.api.workspace import _workspace_scope_from_request
1066
- from latticeai.core.project_sessions import ProjectSessionStore
1067
- from latticeai.runtime.hooks_runtime import (
1068
- bind_builtin_hook_runners,
1069
- bind_trigger_hook_runner,
1070
- )
1071
- from latticeai.runtime.platform_runtime_wiring import (
1072
- build_platform_automation_runtime,
1073
- )
1074
- from latticeai.runtime.router_registration import (
1075
- register_platform_feature_routers,
1076
- )
1077
- from latticeai.services.change_proposals import ChangeProposalService
1078
- from latticeai.services.command_center import CommandCenterService
1079
- from latticeai.services.evidence_actions import EvidenceActionService
1080
- from latticeai.services.tool_dispatch import get_tool_permission
1081
- from latticeai.services.voice_capture import VoiceCaptureService
1082
- from latticeai.tools import resolve_workspace_path
1083
-
1084
- # v2 Agentic Workspace Platform: cross-system wiring.
1085
- platform_automation_runtime = build_platform_automation_runtime(
1086
- model_router=ctx.model_router,
1087
- workspace_store=ctx.WORKSPACE_OS,
1088
- workspace_service=ctx.WORKSPACE_SERVICE,
1089
- plugin_registry=ctx.PLUGIN_REGISTRY,
1090
- get_current_user=ctx.get_current_user,
1091
- workspace_graph=ctx._workspace_graph,
1092
- workspace_scope_from_request=_workspace_scope_from_request,
1093
- get_tool_permission=get_tool_permission,
1094
- hooks=ctx.HOOKS_REGISTRY,
1095
- agent_registry=ctx.AGENT_REGISTRY,
1096
- data_dir=ctx.DATA_DIR,
1097
- append_audit_event=ctx.append_audit_event,
1098
- memory_service=ctx.MEMORY_SERVICE,
1099
- tz_name=getattr(ctx.CONFIG, "timezone", None),
1100
- )
1101
- ctx.adopt(
1102
- platform_automation_runtime,
1103
- "_llm_generate_sync",
1104
- "PLATFORM",
1105
- "_automation_runtime",
1106
- "REVIEW_QUEUE",
1107
- "TRIGGER_SERVICE",
1108
- "AGENT_RUNTIME",
1109
- "RUN_EXECUTOR",
1110
- )
1111
- bind_trigger_hook_runner(
1112
- registry=ctx.HOOKS_REGISTRY, trigger_service=ctx.TRIGGER_SERVICE
1113
- )
1114
- ctx.app.state.run_executor = ctx.RUN_EXECUTOR
1115
- ctx.app.state.run_reconciliation = ctx.RUN_EXECUTOR.reconcile_startup()
1116
- ctx.TRIGGER_SERVICE.start()
1117
-
1118
- bind_builtin_hook_runners(
1119
- registry=ctx.HOOKS_REGISTRY,
1120
- append_audit_event=ctx.append_audit_event,
1121
- get_tool_permission=get_tool_permission,
1122
- classify_sensitive_message=ctx.classify_sensitive_message,
1123
- )
1124
-
1125
- register_platform_feature_routers(
1126
- ctx.app,
1127
- create_plugins_router=create_plugins_router,
1128
- plugin_registry=ctx.PLUGIN_REGISTRY,
1129
- require_user=ctx.require_user,
1130
- require_admin=ctx.require_admin,
1131
- append_audit_event=ctx.append_audit_event,
1132
- platform=ctx.PLATFORM,
1133
- ui_file_response=ctx.ui_file_response,
1134
- static_dir=ctx.STATIC_DIR,
1135
- create_workflow_designer_router=create_workflow_designer_router,
1136
- store=ctx.WORKSPACE_OS,
1137
- get_current_user=ctx.get_current_user,
1138
- workspace_graph=ctx._workspace_graph,
1139
- hooks=ctx.HOOKS_REGISTRY,
1140
- run_executor=ctx.RUN_EXECUTOR,
1141
- trigger_service=ctx.TRIGGER_SERVICE,
1142
- create_agents_router=create_agents_router,
1143
- agent_runtime=ctx.AGENT_RUNTIME,
1144
- create_marketplace_router=create_marketplace_router,
1145
- template_catalog=ctx.TEMPLATE_CATALOG,
1146
- create_realtime_router=create_realtime_router,
1147
- realtime_bus=ctx.REALTIME_BUS,
1148
- )
1149
-
1150
- ctx.app.include_router(
1151
- create_automation_intelligence_router(
1152
- service=ctx.AUTOMATION_INTELLIGENCE,
1153
- store=ctx.WORKSPACE_OS,
1154
- require_user=ctx.require_user,
1155
- gate_read=ctx.PLATFORM.gate_read,
1156
- gate_write=ctx.PLATFORM.gate_write,
1157
- append_audit_event=ctx.append_audit_event,
1158
- workspace_graph=ctx._workspace_graph,
1159
- run_executor=ctx.RUN_EXECUTOR,
1160
- review_queue=ctx.REVIEW_QUEUE,
1161
- )
1162
- )
1163
- # UX funnel metrics (backlog #16): admin-only runtime counters.
1164
- ctx.app.include_router(
1165
- create_funnel_metrics_router(
1166
- service=ctx.FUNNEL_METRICS,
1167
- require_admin=ctx.require_admin,
1168
- )
1169
- )
1170
-
1171
- ctx.set(
1172
- COMMAND_CENTER=CommandCenterService(
1173
- conversation_store=ctx.CONVERSATIONS,
1174
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
1175
- store=ctx.WORKSPACE_OS,
1176
- search_service=ctx.SEARCH_SERVICE,
1177
- brain_intelligence=ctx.BRAIN_INTELLIGENCE,
1178
- automation_intelligence=ctx.AUTOMATION_INTELLIGENCE,
1179
- review_queue=ctx.REVIEW_QUEUE,
1180
- enable_graph=ctx.ENABLE_GRAPH,
1181
- )
1182
- )
1183
- ctx.app.include_router(
1184
- create_command_center_router(
1185
- service=ctx.COMMAND_CENTER,
1186
- require_user=ctx.require_user,
1187
- gate_read=ctx.PLATFORM.gate_read,
1188
- )
1189
- )
1190
-
1191
- ctx.set(
1192
- CHANGE_PROPOSALS=ChangeProposalService(
1193
- review_queue=ctx.REVIEW_QUEUE,
1194
- resolve_path=resolve_workspace_path,
1195
- audit=ctx.append_audit_event,
1196
- )
1197
- )
1198
- # Proposal-first mutations: the agent loop consults the governor so
1199
- # additive creates run with minimal friction while changes and deletions
1200
- # of existing files are staged for review instead of applied.
1201
- ctx.CHAT_AGENT_RUNTIME.deps.change_governor = ctx.CHANGE_PROPOSALS
1202
- ctx.app.include_router(
1203
- create_change_proposals_router(
1204
- service=ctx.CHANGE_PROPOSALS,
1205
- require_user=ctx.require_user,
1206
- gate_read=ctx.PLATFORM.gate_read,
1207
- gate_write=ctx.PLATFORM.gate_write,
1208
- )
1209
- )
1210
-
1211
- # Evidence → action (v9.9.6): an answer's citations become ready-to-send,
1212
- # evidence-scoped follow-ups. Deterministic composition only — execution
1213
- # stays on the chat/file-generation path.
1214
- ctx.set(
1215
- EVIDENCE_ACTIONS_SERVICE=EvidenceActionService(
1216
- node_reader=getattr(ctx.KNOWLEDGE_GRAPH, "get_node", None)
1217
- )
1218
- )
1219
- ctx.app.include_router(
1220
- create_evidence_actions_router(
1221
- service=ctx.EVIDENCE_ACTIONS_SERVICE,
1222
- require_user=ctx.require_user,
1223
- allowed_workspaces_for=ctx._allowed_workspaces_for,
1224
- )
1225
- )
1226
-
1227
- # Voice memo capture (v9.9.7): the shortest path from a thought to the
1228
- # Brain. Transcription is an optional local port — absent, the memo is
1229
- # still stored and the response says it is not searchable. v11.1.0 shares
1230
- # that one port with multi-modal ingestion, so a memo and a scanned
1231
- # recording can never disagree about whether this machine can hear.
1232
- ctx.set(
1233
- VOICE_CAPTURE=VoiceCaptureService(
1234
- pipeline=ctx.INGESTION_PIPELINE if ctx.ENABLE_GRAPH else None,
1235
- transcriber=ctx.MULTIMODAL_PORTS.transcriber,
1236
- )
1237
- )
1238
- ctx.app.include_router(
1239
- create_voice_capture_router(
1240
- service=ctx.VOICE_CAPTURE,
1241
- require_user=ctx.require_user,
1242
- gate_write=ctx.PLATFORM.gate_write,
1243
- append_audit_event=ctx.append_audit_event,
1244
- )
1245
- )
1246
-
1247
- # Multi-turn project loop (v9.9.6): work spanning several runs keeps its
1248
- # files, open TODOs, and last honest verification in one project session.
1249
- ctx.set(PROJECT_SESSIONS=ProjectSessionStore(ctx.DATA_DIR / "project_sessions"))
1250
- ctx.app.include_router(
1251
- create_project_sessions_router(
1252
- store=ctx.PROJECT_SESSIONS,
1253
- require_user=ctx.require_user,
1254
- gate_read=ctx.PLATFORM.gate_read,
1255
- gate_write=ctx.PLATFORM.gate_write,
1256
- )
1257
- )
1258
-
1259
-
1260
- # ── phase 8: interaction routers ─────────────────────────────────────────────
1261
- def phase_interaction(ctx: RuntimeContext) -> None:
1262
- """Model, chat, search, tools, hooks, memory and brain tail routers."""
1263
- ctx.enter("interaction")
1264
-
1265
- from fastapi import HTTPException
1266
-
1267
- from latticeai.api.agent_registry import create_agent_registry_router
1268
- from latticeai.api.brain_intelligence import create_brain_intelligence_router
1269
- from latticeai.api.browser import create_browser_router
1270
- from latticeai.api.chat import create_chat_router
1271
- from latticeai.api.garden import create_garden_router
1272
- from latticeai.api.health import create_health_router
1273
- from latticeai.api.hooks import create_hooks_router
1274
- from latticeai.api.memory import create_memory_router
1275
- from latticeai.api.models import create_models_router
1276
- from latticeai.api.network import create_network_router
1277
- from latticeai.api.portability import create_portability_router
1278
- from latticeai.api.review_queue import create_review_queue_router
1279
- from latticeai.api.search import create_search_router
1280
- from latticeai.api.setup import create_setup_router
1281
- from latticeai.api.tools import create_tools_router
1282
- from latticeai.core.model_compat import (
1283
- list_cached_profiles as _list_compat_profiles,
1284
- )
1285
- from latticeai.runtime.chat_wiring import build_interaction_contexts
1286
- from latticeai.runtime.model_wiring import register_model_runtime_routers
1287
- from latticeai.runtime.platform_services_runtime import build_brain_network
1288
- from latticeai.runtime.review_wiring import build_review_run_now_runner
1289
- from latticeai.runtime.router_registration import (
1290
- register_health_and_model_routers,
1291
- register_interaction_routers,
1292
- register_review_and_brain_tail_routers,
1293
- )
1294
- from latticeai.services.model_runtime import (
1295
- CLOUD_VERIFY_TTL_SECONDS,
1296
- ENGINE_MODEL_CATALOG,
1297
- MODEL_ENGINE_ALIASES,
1298
- download_hf_model,
1299
- ensure_ollama_server,
1300
- filter_lower_family_versions,
1301
- local_binary,
1302
- normalize_local_model_request,
1303
- sse_event,
1304
- )
1305
-
1306
- service = ctx.model_runtime_service
1307
- ctx.set(
1308
- model_runtime=register_model_runtime_routers(
1309
- app=ctx.app,
1310
- create_health_router=create_health_router,
1311
- create_models_router=create_models_router,
1312
- register_health_and_model_routers=register_health_and_model_routers,
1313
- model_router=ctx.model_router,
1314
- runtime_service=service,
1315
- runtime_features=service.runtime_features,
1316
- is_public_mode=ctx.IS_PUBLIC_MODE,
1317
- engine_status=service.engine_status,
1318
- get_current_user=ctx.get_current_user,
1319
- require_auth=ctx.REQUIRE_AUTH,
1320
- app_version=ctx.APP_VERSION,
1321
- app_mode=ctx.APP_MODE,
1322
- require_user=ctx.require_user,
1323
- require_admin=ctx.require_admin,
1324
- load_users=ctx.load_users,
1325
- get_user_role=ctx.get_user_role,
1326
- install_engine=service.install_engine,
1327
- verify_cloud_models=service.verify_cloud_models,
1328
- normalize_local_model_request=normalize_local_model_request,
1329
- download_hf_model=download_hf_model,
1330
- prepare_and_load_model=service.prepare_and_load_model,
1331
- prepare_and_load_model_stream=service.prepare_and_load_model_stream,
1332
- sse_event=sse_event,
1333
- ensure_ollama_server=ensure_ollama_server,
1334
- local_binary=local_binary,
1335
- filter_lower_family_versions=filter_lower_family_versions,
1336
- list_compat_profiles=_list_compat_profiles,
1337
- set_user_api_key=ctx.set_user_api_key,
1338
- engine_model_catalog=ENGINE_MODEL_CATALOG,
1339
- model_engine_aliases=MODEL_ENGINE_ALIASES,
1340
- cloud_verify_ttl_seconds=CLOUD_VERIFY_TTL_SECONDS,
1341
- allow_local_models=ctx.ALLOW_LOCAL_MODELS,
1342
- )
1343
- )
1344
-
1345
- _tool_router_context, interaction_router_context = build_interaction_contexts(
1346
- config=ctx.CONFIG,
1347
- ingestion_pipeline=ctx.INGESTION_PIPELINE,
1348
- data_dir=ctx.DATA_DIR,
1349
- static_dir=ctx.STATIC_DIR,
1350
- model_router=ctx.model_router,
1351
- require_user=ctx.require_user,
1352
- require_admin=ctx.require_admin,
1353
- get_current_user=ctx.get_current_user,
1354
- clear_history=ctx.clear_history,
1355
- append_audit_event=ctx.append_audit_event,
1356
- enforce_rate_limit=ctx.enforce_rate_limit,
1357
- bytes_match_extension=ctx._bytes_match_extension,
1358
- classify_sensitive_message=ctx.classify_sensitive_message,
1359
- save_to_history=ctx.save_to_history,
1360
- enable_graph=ctx.ENABLE_GRAPH,
1361
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
1362
- require_graph=ctx._require_graph,
1363
- local_kg_watcher=ctx.LOCAL_KG_WATCHER,
1364
- load_mcp_installs=ctx.load_mcp_installs,
1365
- recommend_mcps=ctx.recommend_mcps,
1366
- install_mcp=ctx.install_mcp,
1367
- mcp_public_item=ctx.mcp_public_item,
1368
- hooks=ctx.HOOKS_REGISTRY,
1369
- workspace_service=ctx.WORKSPACE_SERVICE,
1370
- chat_context=ctx.app_context,
1371
- search_service=ctx.SEARCH_SERVICE,
1372
- allowed_workspaces_for=ctx._allowed_workspaces_for,
1373
- embedding_info=ctx._embedding_info,
1374
- agent_registry=ctx.AGENT_REGISTRY,
1375
- memory_service=ctx.MEMORY_SERVICE,
1376
- platform=ctx.PLATFORM,
1377
- active_model_getter=lambda: ctx.model_router.current_model_id or "",
1378
- brain_intelligence=ctx.BRAIN_INTELLIGENCE,
1379
- )
1380
- register_interaction_routers(
1381
- ctx.app,
1382
- interaction_context=interaction_router_context,
1383
- create_chat_router=create_chat_router,
1384
- create_search_router=create_search_router,
1385
- create_tools_router=create_tools_router,
1386
- create_hooks_router=create_hooks_router,
1387
- create_agent_registry_router=create_agent_registry_router,
1388
- create_memory_router=create_memory_router,
1389
- create_brain_intelligence_router=create_brain_intelligence_router,
1390
- )
1391
-
1392
- register_review_and_brain_tail_routers(
1393
- ctx.app,
1394
- create_review_queue_router=create_review_queue_router,
1395
- review_queue=ctx.REVIEW_QUEUE,
1396
- require_user=ctx.require_user,
1397
- gate_read=ctx.PLATFORM.gate_read,
1398
- gate_write=ctx.PLATFORM.gate_write,
1399
- run_review_item=build_review_run_now_runner(ctx.PLATFORM, HTTPException),
1400
- append_audit_event=ctx.append_audit_event,
1401
- # Approving a change_proposal from the Review Center applies the
1402
- # staged content through the same service the agent governor uses.
1403
- change_proposals=ctx.CHANGE_PROPOSALS,
1404
- create_browser_router=create_browser_router,
1405
- ingestion_pipeline=ctx.INGESTION_PIPELINE,
1406
- workspace_service=ctx.WORKSPACE_SERVICE,
1407
- create_portability_router=create_portability_router,
1408
- kg_portability=ctx.KG_PORTABILITY,
1409
- require_admin=ctx.require_admin,
1410
- build_brain_network=build_brain_network,
1411
- device_identity=ctx.DEVICE_IDENTITY,
1412
- data_dir=ctx.DATA_DIR,
1413
- create_network_router=create_network_router,
1414
- create_garden_router=create_garden_router,
1415
- gardener=ctx.gardener,
1416
- create_setup_router=create_setup_router,
1417
- model_router=ctx.model_router,
1418
- knowledge_graph=ctx.KNOWLEDGE_GRAPH,
1419
- )
1420
-
1421
-
1422
- #: The build order. Exported so the ordering test reads the same list the
1423
- #: factory runs, rather than a copy that can drift.
1424
- BUILD_PHASES = (
1425
- phase_platform,
1426
- phase_config,
1427
- phase_identity,
1428
- phase_brain,
1429
- phase_domain,
1430
- phase_web,
1431
- phase_services,
1432
- phase_foundation_routes,
1433
- phase_platform_features,
1434
- phase_interaction,
1435
- )
1436
-
1437
-
1438
- __all__ = [
1439
- "BUILD_PHASES",
1440
- "phase_brain",
1441
- "phase_domain",
1442
- "phase_config",
1443
- "phase_foundation_routes",
1444
- "phase_identity",
1445
- "phase_interaction",
1446
- "phase_platform",
1447
- "phase_platform_features",
1448
- "phase_services",
1449
- "phase_web",
1450
- ]