ltcai 11.5.1 → 11.6.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 (473) hide show
  1. package/README.md +92 -117
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +129 -0
  4. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  5. package/docs/DEVELOPMENT.md +8 -6
  6. package/docs/ENTERPRISE.md +2 -1
  7. package/docs/HYBRID_CLOUD_KG_STREAMING.md +8 -4
  8. package/docs/MULTI_AGENT_RUNTIME.md +12 -5
  9. package/docs/ONBOARDING.md +1 -1
  10. package/docs/OPERATIONS.md +6 -3
  11. package/docs/REALTIME_COLLABORATION.md +1 -1
  12. package/docs/TRUST_MODEL.md +1 -1
  13. package/docs/WHY_LATTICE.md +1 -1
  14. package/docs/WORKFLOW_DESIGNER.md +3 -2
  15. package/docs/architecture.md +4 -4
  16. package/docs/kg-schema.md +1 -1
  17. package/docs/v11.5.2_TIGHT_SHIP_PLAN.md +144 -0
  18. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  19. package/lattice_brain/__init__.py +42 -79
  20. package/lattice_brain/graph/__init__.py +9 -24
  21. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  22. package/lattice_brain/ingestion/__init__.py +19 -58
  23. package/lattice_brain/ingestion/pipeline.py +20 -398
  24. package/lattice_brain/multimodal/__init__.py +7 -18
  25. package/lattice_brain/multimodal/images.py +6 -264
  26. package/lattice_brain/multimodal/video.py +8 -247
  27. package/lattice_brain/runtime/__init__.py +8 -79
  28. package/lattice_brain/runtime/hooks.py +22 -597
  29. package/latticeai/__init__.py +1 -1
  30. package/latticeai/api/agent_worker_seam.py +7 -80
  31. package/latticeai/api/health.py +26 -20
  32. package/latticeai/api/local_files.py +25 -608
  33. package/latticeai/api/models.py +12 -87
  34. package/latticeai/api/search.py +17 -231
  35. package/latticeai/api/tools.py +69 -750
  36. package/latticeai/api/voice_capture.py +8 -70
  37. package/latticeai/api/worker_compute.py +842 -0
  38. package/latticeai/api/worker_seams.py +216 -0
  39. package/latticeai/app_factory.py +23 -232
  40. package/latticeai/cli/entrypoint.py +36 -249
  41. package/latticeai/cli/runtime.py +7 -3
  42. package/latticeai/core/agent_permission.py +16 -91
  43. package/latticeai/core/config.py +47 -3
  44. package/latticeai/core/csrf.py +28 -2
  45. package/latticeai/core/embedding_providers/__init__.py +3 -5
  46. package/latticeai/core/embedding_providers/base.py +1 -1
  47. package/latticeai/core/embedding_providers/text.py +1 -1
  48. package/latticeai/core/http_origin.py +146 -0
  49. package/latticeai/core/io_utils.py +2 -11
  50. package/latticeai/core/messages.py +94 -531
  51. package/latticeai/core/model_compat.py +2 -8
  52. package/latticeai/core/module_probe.py +37 -0
  53. package/latticeai/core/security.py +13 -0
  54. package/latticeai/core/sessions.py +3 -2
  55. package/latticeai/core/sse.py +25 -0
  56. package/latticeai/core/users.py +0 -9
  57. package/latticeai/models/router/documents.py +0 -35
  58. package/latticeai/models/router/registry.py +0 -4
  59. package/latticeai/runtime/access_runtime.py +16 -18
  60. package/latticeai/runtime/bootstrap.py +10 -21
  61. package/latticeai/runtime/brain_runtime.py +35 -43
  62. package/latticeai/runtime/build_phases/__init__.py +21 -37
  63. package/latticeai/runtime/build_phases/features.py +101 -389
  64. package/latticeai/runtime/build_phases/foundation.py +82 -392
  65. package/latticeai/runtime/build_phases/web.py +91 -406
  66. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  67. package/latticeai/runtime/lifespan_runtime.py +7 -16
  68. package/latticeai/runtime/platform_services_runtime.py +1 -15
  69. package/latticeai/runtime/runtime_context.py +14 -143
  70. package/latticeai/runtime/security_runtime.py +10 -103
  71. package/latticeai/services/architecture_readiness.py +82 -43
  72. package/latticeai/services/model_capability_registry.py +0 -15
  73. package/latticeai/services/model_catalog.py +0 -7
  74. package/latticeai/services/model_loading.py +3 -2
  75. package/latticeai/services/model_runtime/__init__.py +2 -68
  76. package/latticeai/services/model_runtime/engines.py +27 -128
  77. package/latticeai/services/model_runtime/loading.py +2 -2
  78. package/latticeai/services/model_runtime/service.py +4 -28
  79. package/latticeai/services/p_reinforce.py +10 -261
  80. package/latticeai/services/product_readiness.py +31 -38
  81. package/latticeai/services/search_service.py +37 -795
  82. package/latticeai/services/tool_dispatch.py +30 -390
  83. package/latticeai/services/voice_capture.py +13 -107
  84. package/latticeai/tools/__init__.py +22 -49
  85. package/latticeai/tools/commands.py +0 -163
  86. package/latticeai/tools/computer.py +0 -39
  87. package/latticeai/tools/documents.py +1 -134
  88. package/latticeai/tools/filesystem.py +1 -247
  89. package/latticeai/tools/knowledge.py +1 -52
  90. package/latticeai/tools/local_files.py +0 -20
  91. package/latticeai/worker_app.py +75 -0
  92. package/package.json +3 -6
  93. package/requirements.txt +0 -5
  94. package/scripts/agent_eval.py +16 -28
  95. package/scripts/brain_quality_eval.py +20 -183
  96. package/scripts/bump_version.py +19 -6
  97. package/scripts/check_current_release_docs.mjs +7 -4
  98. package/scripts/check_legacy_debt.mjs +1 -1
  99. package/scripts/check_openapi_drift.mjs +10 -0
  100. package/scripts/check_server_i18n.mjs +2 -18
  101. package/scripts/compose_openapi.py +377 -0
  102. package/scripts/export_openapi.py +32 -4
  103. package/scripts/gen_messages_catalog_fixture.py +302 -0
  104. package/scripts/gen_openapi_fragments.py +365 -0
  105. package/scripts/gen_redact_fixture.py +196 -0
  106. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  107. package/scripts/generate_agent_parity_fixtures.py +33 -14
  108. package/scripts/openapi_route_families.json +2161 -0
  109. package/scripts/release_screen_claims.json +31 -0
  110. package/scripts/run_integration_tests.mjs +134 -29
  111. package/scripts/run_sidecar_e2e.mjs +104 -11
  112. package/scripts/wheel_smoke.py +32 -22
  113. package/src-tauri/Cargo.lock +375 -9
  114. package/src-tauri/Cargo.toml +1 -1
  115. package/src-tauri/src/backend.rs +223 -43
  116. package/src-tauri/src/main.rs +27 -17
  117. package/src-tauri/src/topology.rs +58 -88
  118. package/src-tauri/tauri.conf.json +2 -3
  119. package/static/app/asset-manifest.json +41 -41
  120. package/static/app/assets/{Act-Drs_jd-O.js → Act-CToZnOHz.js} +1 -1
  121. package/static/app/assets/{AdminConsole-BqNx5oF6.js → AdminConsole-D5kbrBu8.js} +1 -1
  122. package/static/app/assets/{Brain-C7bdNmz-.js → Brain-C6zCdv4S.js} +1 -1
  123. package/static/app/assets/{BrainHome-DcTnINcI.js → BrainHome-D4n3EDUt.js} +1 -1
  124. package/static/app/assets/{BrainSignals-Df1MRIPD.js → BrainSignals-CzeZ-vQ9.js} +1 -1
  125. package/static/app/assets/{Capture-Dgsxvnbx.js → Capture-DPcyTczq.js} +1 -1
  126. package/static/app/assets/{Chronicle-BrkTxJK1.js → Chronicle-D3Nx-oWS.js} +1 -1
  127. package/static/app/assets/{CommandPalette-DDziw7Oj.js → CommandPalette-DPPvpp28.js} +1 -1
  128. package/static/app/assets/{Library-CYxeuraA.js → Library-Co2qTu3I.js} +1 -1
  129. package/static/app/assets/{LivingBrain-CNp6mvCm.js → LivingBrain-Bsmzwy8_.js} +1 -1
  130. package/static/app/assets/{ProductFlow-BVsowu3Z.js → ProductFlow-CBLdmesT.js} +1 -1
  131. package/static/app/assets/{ReviewCard-D1LbOfJS.js → ReviewCard-CfR6DLs7.js} +1 -1
  132. package/static/app/assets/{System-DcRAq7wj.js → System-Ce-P2YL6.js} +1 -1
  133. package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
  134. package/static/app/assets/{bot-BK2xQ9mN.js → bot-C5-XIww2.js} +1 -1
  135. package/static/app/assets/{brain-BZNztFBb.js → brain-CFRJUvdX.js} +1 -1
  136. package/static/app/assets/{button-CjueubVZ.js → button-DI1KcQP-.js} +1 -1
  137. package/static/app/assets/circle-check-CY20XMCh.js +1 -0
  138. package/static/app/assets/{circle-pause-HRjSmpyY.js → circle-pause-C8HHyood.js} +1 -1
  139. package/static/app/assets/{circle-play-CMcKDVwu.js → circle-play-Cufc235E.js} +1 -1
  140. package/static/app/assets/{cpu-B_MyTfYr.js → cpu-DW2_JVsv.js} +1 -1
  141. package/static/app/assets/{download-CTy0EByV.js → download-gbdKTBb8.js} +1 -1
  142. package/static/app/assets/{folder-open-DF6bLhTg.js → folder-open-C9ydyX33.js} +1 -1
  143. package/static/app/assets/{hard-drive-CpWHG77C.js → hard-drive-BxNMzBM3.js} +1 -1
  144. package/static/app/assets/index-BovSWRmQ.css +2 -0
  145. package/static/app/assets/{index-Dd6abJHX.js → index-q9XyDY7A.js} +3 -3
  146. package/static/app/assets/{input-bVgIRN3s.js → input-GLD94Y_K.js} +1 -1
  147. package/static/app/assets/{link-2-bg6CG5Vk.js → link-2-B4Y0gz1S.js} +1 -1
  148. package/static/app/assets/{permissionCopy-DFTf1HDZ.js → permissionCopy-CxrV0fvU.js} +1 -1
  149. package/static/app/assets/{primitives-D7D-sQ3T.js → primitives-2ANdAsu2.js} +1 -1
  150. package/static/app/assets/search-DTxtjQ07.js +1 -0
  151. package/static/app/assets/{share-2-BLzo7U4L.js → share-2-DxArfiuI.js} +1 -1
  152. package/static/app/assets/{shield-alert-BmngqnsJ.js → shield-alert-CgftuFFs.js} +1 -1
  153. package/static/app/assets/{textarea-DOSlAQQ3.js → textarea-BHVBAoCr.js} +1 -1
  154. package/static/app/assets/{useFocusTrap-pZhgeee4.js → useFocusTrap-Dzq2j_OG.js} +1 -1
  155. package/static/app/assets/{useMutation-BMDwNk4I.js → useMutation-BcqfMLNq.js} +1 -1
  156. package/static/app/assets/{useQuery-CSjKttRo.js → useQuery-DBzLKCOv.js} +1 -1
  157. package/static/app/assets/{utils-D3u_yv7B.js → utils-B-Aah1bO.js} +1 -1
  158. package/static/app/assets/{workspace-H3bMjYXC.js → workspace-B6fFhoko.js} +1 -1
  159. package/static/app/index.html +4 -4
  160. package/static/sw.js +1 -1
  161. package/desktop/electron/README.md +0 -9
  162. package/desktop/electron/main.cjs +0 -58
  163. package/desktop/electron/preload.cjs +0 -5
  164. package/lattice_brain/archive.py +0 -522
  165. package/lattice_brain/context.py +0 -325
  166. package/lattice_brain/conversations.py +0 -382
  167. package/lattice_brain/core.py +0 -82
  168. package/lattice_brain/graph/_kg_contract.py +0 -249
  169. package/lattice_brain/graph/curator.py +0 -676
  170. package/lattice_brain/graph/discovery.py +0 -595
  171. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  172. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  173. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  174. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  175. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  176. package/lattice_brain/graph/documents.py +0 -380
  177. package/lattice_brain/graph/fusion.py +0 -395
  178. package/lattice_brain/graph/identity.py +0 -175
  179. package/lattice_brain/graph/image_vectors.py +0 -230
  180. package/lattice_brain/graph/ingest.py +0 -829
  181. package/lattice_brain/graph/network.py +0 -205
  182. package/lattice_brain/graph/proactive.py +0 -724
  183. package/lattice_brain/graph/projection/__init__.py +0 -42
  184. package/lattice_brain/graph/projection/curation.py +0 -500
  185. package/lattice_brain/graph/projection/v2_schema.py +0 -546
  186. package/lattice_brain/graph/provenance.py +0 -524
  187. package/lattice_brain/graph/rerank.py +0 -163
  188. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  189. package/lattice_brain/graph/retrieval/context.py +0 -197
  190. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  191. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  192. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  193. package/lattice_brain/graph/retrieval/signals.py +0 -95
  194. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  195. package/lattice_brain/graph/retrieval_policy.py +0 -180
  196. package/lattice_brain/graph/retrieval_reads.py +0 -769
  197. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  198. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  199. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  200. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  201. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  202. package/lattice_brain/graph/schema.py +0 -792
  203. package/lattice_brain/graph/store.py +0 -268
  204. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  205. package/lattice_brain/graph/vector_index/base.py +0 -170
  206. package/lattice_brain/graph/vector_index/brute_force.py +0 -114
  207. package/lattice_brain/graph/vector_index/hnsw.py +0 -293
  208. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  209. package/lattice_brain/graph/vector_index/quantized.py +0 -151
  210. package/lattice_brain/graph/vector_index/selector.py +0 -161
  211. package/lattice_brain/graph/write_master.py +0 -308
  212. package/lattice_brain/ingestion/_contract.py +0 -90
  213. package/lattice_brain/ingestion/folder_scan.py +0 -57
  214. package/lattice_brain/ingestion/folders.py +0 -258
  215. package/lattice_brain/ingestion/jobs_api.py +0 -107
  216. package/lattice_brain/ingestion/routing.py +0 -295
  217. package/lattice_brain/ingestion_jobs.py +0 -383
  218. package/lattice_brain/memory.py +0 -102
  219. package/lattice_brain/portability/__init__.py +0 -90
  220. package/lattice_brain/portability/_contract.py +0 -42
  221. package/lattice_brain/portability/backups.py +0 -338
  222. package/lattice_brain/portability/bundles.py +0 -136
  223. package/lattice_brain/portability/constants.py +0 -93
  224. package/lattice_brain/portability/fsops.py +0 -138
  225. package/lattice_brain/portability/service.py +0 -41
  226. package/lattice_brain/portability/sharing.py +0 -714
  227. package/lattice_brain/quality.py +0 -592
  228. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  229. package/lattice_brain/runtime/agent_runtime.py +0 -859
  230. package/lattice_brain/runtime/contracts.py +0 -460
  231. package/lattice_brain/runtime/multi_agent.py +0 -942
  232. package/lattice_brain/runtime/statuses.py +0 -10
  233. package/lattice_brain/sealed_box.py +0 -244
  234. package/lattice_brain/self_model.py +0 -675
  235. package/lattice_brain/sensitivity.py +0 -94
  236. package/lattice_brain/storage/__init__.py +0 -22
  237. package/lattice_brain/storage/base.py +0 -100
  238. package/lattice_brain/storage/docker.py +0 -105
  239. package/lattice_brain/storage/factory.py +0 -31
  240. package/lattice_brain/storage/migration.py +0 -191
  241. package/lattice_brain/storage/postgres.py +0 -123
  242. package/lattice_brain/storage/sqlite.py +0 -143
  243. package/lattice_brain/synthesis.py +0 -824
  244. package/lattice_brain/workflow.py +0 -497
  245. package/latticeai/api/admin.py +0 -471
  246. package/latticeai/api/agent_registry.py +0 -105
  247. package/latticeai/api/agents.py +0 -228
  248. package/latticeai/api/auth.py +0 -360
  249. package/latticeai/api/automation_intelligence.py +0 -401
  250. package/latticeai/api/brain_intelligence.py +0 -199
  251. package/latticeai/api/browser.py +0 -493
  252. package/latticeai/api/change_proposals.py +0 -89
  253. package/latticeai/api/chat.py +0 -572
  254. package/latticeai/api/chat_agent_http.py +0 -901
  255. package/latticeai/api/chat_contracts.py +0 -73
  256. package/latticeai/api/chat_documents.py +0 -276
  257. package/latticeai/api/chat_helpers.py +0 -463
  258. package/latticeai/api/chat_history.py +0 -101
  259. package/latticeai/api/chat_hybrid.py +0 -113
  260. package/latticeai/api/chat_intents.py +0 -646
  261. package/latticeai/api/chat_stream.py +0 -216
  262. package/latticeai/api/chronicle.py +0 -63
  263. package/latticeai/api/command_center.py +0 -51
  264. package/latticeai/api/computer_use.py +0 -485
  265. package/latticeai/api/evidence_actions.py +0 -48
  266. package/latticeai/api/features.py +0 -70
  267. package/latticeai/api/funnel_metrics.py +0 -31
  268. package/latticeai/api/garden.py +0 -34
  269. package/latticeai/api/hooks.py +0 -165
  270. package/latticeai/api/index_jobs.py +0 -145
  271. package/latticeai/api/invitations.py +0 -100
  272. package/latticeai/api/knowledge_graph.py +0 -536
  273. package/latticeai/api/marketplace.py +0 -105
  274. package/latticeai/api/mcp.py +0 -482
  275. package/latticeai/api/memory.py +0 -270
  276. package/latticeai/api/network.py +0 -81
  277. package/latticeai/api/network_boundary.py +0 -225
  278. package/latticeai/api/permission_mode.py +0 -61
  279. package/latticeai/api/permissions.py +0 -410
  280. package/latticeai/api/plugins.py +0 -126
  281. package/latticeai/api/portability.py +0 -391
  282. package/latticeai/api/project_sessions.py +0 -114
  283. package/latticeai/api/realtime.py +0 -118
  284. package/latticeai/api/review_queue.py +0 -364
  285. package/latticeai/api/security_dashboard.py +0 -604
  286. package/latticeai/api/setup.py +0 -319
  287. package/latticeai/api/static_routes.py +0 -351
  288. package/latticeai/api/ui_redirects.py +0 -26
  289. package/latticeai/api/workflow_designer.py +0 -394
  290. package/latticeai/api/workspace.py +0 -856
  291. package/latticeai/api/workspace_scope.py +0 -125
  292. package/latticeai/core/agent/__init__.py +0 -93
  293. package/latticeai/core/agent/_contract.py +0 -79
  294. package/latticeai/core/agent/context.py +0 -57
  295. package/latticeai/core/agent/deps.py +0 -125
  296. package/latticeai/core/agent/execution.py +0 -622
  297. package/latticeai/core/agent/planning.py +0 -145
  298. package/latticeai/core/agent/recovery.py +0 -157
  299. package/latticeai/core/agent/runtime.py +0 -210
  300. package/latticeai/core/agent/verification.py +0 -231
  301. package/latticeai/core/agent_eval.py +0 -739
  302. package/latticeai/core/agent_helpers.py +0 -493
  303. package/latticeai/core/agent_profiles.py +0 -110
  304. package/latticeai/core/agent_prompts.py +0 -171
  305. package/latticeai/core/agent_registry.py +0 -232
  306. package/latticeai/core/agent_state.py +0 -41
  307. package/latticeai/core/agent_trace.py +0 -104
  308. package/latticeai/core/artifact_ledger.py +0 -109
  309. package/latticeai/core/audit.py +0 -260
  310. package/latticeai/core/builtin_hooks.py +0 -105
  311. package/latticeai/core/context_builder.py +0 -394
  312. package/latticeai/core/document_generator.py +0 -103
  313. package/latticeai/core/enterprise.py +0 -157
  314. package/latticeai/core/enterprise_admin.py +0 -158
  315. package/latticeai/core/file_generation/__init__.py +0 -115
  316. package/latticeai/core/file_generation/bundles.py +0 -76
  317. package/latticeai/core/file_generation/extraction.py +0 -154
  318. package/latticeai/core/file_generation/inference.py +0 -235
  319. package/latticeai/core/file_generation/orchestration.py +0 -152
  320. package/latticeai/core/file_generation/prompting.py +0 -117
  321. package/latticeai/core/file_generation/repair.py +0 -114
  322. package/latticeai/core/file_generation/sanitize.py +0 -61
  323. package/latticeai/core/file_generation/validation.py +0 -201
  324. package/latticeai/core/graph_curator.py +0 -11
  325. package/latticeai/core/hooks.py +0 -11
  326. package/latticeai/core/invitations.py +0 -131
  327. package/latticeai/core/legacy_compatibility.py +0 -243
  328. package/latticeai/core/local_embeddings.py +0 -104
  329. package/latticeai/core/logging_safety.py +0 -46
  330. package/latticeai/core/marketplace.py +0 -293
  331. package/latticeai/core/mcp_catalog.py +0 -452
  332. package/latticeai/core/mcp_registry.py +0 -506
  333. package/latticeai/core/multi_agent.py +0 -11
  334. package/latticeai/core/network_boundary.py +0 -168
  335. package/latticeai/core/oidc.py +0 -208
  336. package/latticeai/core/plugins.py +0 -432
  337. package/latticeai/core/product_hardening.py +0 -218
  338. package/latticeai/core/project_sessions.py +0 -337
  339. package/latticeai/core/realtime.py +0 -238
  340. package/latticeai/core/run_explain.py +0 -423
  341. package/latticeai/core/run_store.py +0 -252
  342. package/latticeai/core/timezones.py +0 -80
  343. package/latticeai/core/workflow_engine.py +0 -11
  344. package/latticeai/core/workspace_computer_memory.py +0 -84
  345. package/latticeai/core/workspace_graph_trace.py +0 -161
  346. package/latticeai/core/workspace_indexing.py +0 -102
  347. package/latticeai/core/workspace_memory.py +0 -77
  348. package/latticeai/core/workspace_onboarding.py +0 -104
  349. package/latticeai/core/workspace_os.py +0 -988
  350. package/latticeai/core/workspace_os_constants.py +0 -126
  351. package/latticeai/core/workspace_os_state.py +0 -180
  352. package/latticeai/core/workspace_os_utils.py +0 -69
  353. package/latticeai/core/workspace_permissions.py +0 -101
  354. package/latticeai/core/workspace_plugins.py +0 -97
  355. package/latticeai/core/workspace_relationships.py +0 -99
  356. package/latticeai/core/workspace_reorganization.py +0 -335
  357. package/latticeai/core/workspace_review_items.py +0 -118
  358. package/latticeai/core/workspace_runs.py +0 -732
  359. package/latticeai/core/workspace_skills.py +0 -114
  360. package/latticeai/core/workspace_snapshots.py +0 -198
  361. package/latticeai/core/workspace_timeline.py +0 -110
  362. package/latticeai/integrations/__init__.py +0 -0
  363. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  364. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  365. package/latticeai/integrations/telegram_bot/config.py +0 -86
  366. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  367. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  368. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  369. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  370. package/latticeai/runtime/audit_runtime.py +0 -76
  371. package/latticeai/runtime/automation_runtime.py +0 -81
  372. package/latticeai/runtime/chat_wiring.py +0 -141
  373. package/latticeai/runtime/context_runtime.py +0 -66
  374. package/latticeai/runtime/feature_toggle_wiring.py +0 -163
  375. package/latticeai/runtime/history_runtime.py +0 -163
  376. package/latticeai/runtime/history_writer.py +0 -138
  377. package/latticeai/runtime/hooks_runtime.py +0 -77
  378. package/latticeai/runtime/model_wiring.py +0 -68
  379. package/latticeai/runtime/namespace_runtime.py +0 -163
  380. package/latticeai/runtime/network_boundary_wiring.py +0 -128
  381. package/latticeai/runtime/network_config_runtime.py +0 -56
  382. package/latticeai/runtime/permission_mode_wiring.py +0 -119
  383. package/latticeai/runtime/persistence_runtime.py +0 -159
  384. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  385. package/latticeai/runtime/review_wiring.py +0 -42
  386. package/latticeai/runtime/router_registration.py +0 -689
  387. package/latticeai/runtime/sso_config_runtime.py +0 -128
  388. package/latticeai/runtime/user_key_runtime.py +0 -106
  389. package/latticeai/runtime/web_runtime.py +0 -92
  390. package/latticeai/server_app.py +0 -51
  391. package/latticeai/services/app_context.py +0 -130
  392. package/latticeai/services/automation_execution.py +0 -266
  393. package/latticeai/services/automation_intelligence.py +0 -614
  394. package/latticeai/services/brain_automation.py +0 -191
  395. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  396. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  397. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  398. package/latticeai/services/brain_intelligence/constants.py +0 -47
  399. package/latticeai/services/brain_intelligence/digest.py +0 -258
  400. package/latticeai/services/brain_intelligence/health.py +0 -331
  401. package/latticeai/services/brain_intelligence/proposals.py +0 -264
  402. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  403. package/latticeai/services/brain_intelligence/service.py +0 -48
  404. package/latticeai/services/change_proposals.py +0 -505
  405. package/latticeai/services/chat_service.py +0 -243
  406. package/latticeai/services/chronicle.py +0 -557
  407. package/latticeai/services/cloud_egress_audit.py +0 -85
  408. package/latticeai/services/cloud_extraction.py +0 -129
  409. package/latticeai/services/cloud_streaming.py +0 -268
  410. package/latticeai/services/cloud_token_guard.py +0 -84
  411. package/latticeai/services/command_center.py +0 -548
  412. package/latticeai/services/evidence_actions.py +0 -255
  413. package/latticeai/services/feature_toggles.py +0 -502
  414. package/latticeai/services/folder_watch.py +0 -520
  415. package/latticeai/services/funnel_metrics.py +0 -307
  416. package/latticeai/services/hybrid_chat.py +0 -316
  417. package/latticeai/services/hybrid_context.py +0 -228
  418. package/latticeai/services/hybrid_policy.py +0 -178
  419. package/latticeai/services/ingestion.py +0 -11
  420. package/latticeai/services/interop_bridges.py +0 -978
  421. package/latticeai/services/kg_portability.py +0 -11
  422. package/latticeai/services/local_knowledge.py +0 -465
  423. package/latticeai/services/memory_service/__init__.py +0 -52
  424. package/latticeai/services/memory_service/_contract.py +0 -100
  425. package/latticeai/services/memory_service/brief.py +0 -431
  426. package/latticeai/services/memory_service/constants.py +0 -57
  427. package/latticeai/services/memory_service/maintenance.py +0 -138
  428. package/latticeai/services/memory_service/manager.py +0 -186
  429. package/latticeai/services/memory_service/proof.py +0 -136
  430. package/latticeai/services/memory_service/recall.py +0 -225
  431. package/latticeai/services/memory_service/service.py +0 -48
  432. package/latticeai/services/memory_service/stores.py +0 -110
  433. package/latticeai/services/model_recommendation.py +0 -224
  434. package/latticeai/services/model_runtime/cloud.py +0 -87
  435. package/latticeai/services/multimodal_streaming.py +0 -129
  436. package/latticeai/services/network_boundary_service.py +0 -154
  437. package/latticeai/services/obsidian_bridge.py +0 -609
  438. package/latticeai/services/openai_compatible_adapter.py +0 -101
  439. package/latticeai/services/permission_mode_service.py +0 -170
  440. package/latticeai/services/platform_runtime.py +0 -366
  441. package/latticeai/services/review_queue.py +0 -380
  442. package/latticeai/services/router_context.py +0 -59
  443. package/latticeai/services/run_executor.py +0 -387
  444. package/latticeai/services/self_model_service.py +0 -171
  445. package/latticeai/services/setup_detection.py +0 -81
  446. package/latticeai/services/triggers.py +0 -378
  447. package/latticeai/services/upload_service.py +0 -185
  448. package/latticeai/services/workspace_service.py +0 -171
  449. package/latticeai/setup/__init__.py +0 -25
  450. package/latticeai/setup/auto_setup.py +0 -857
  451. package/latticeai/setup/demo_corpus.py +0 -98
  452. package/latticeai/setup/wizard/__init__.py +0 -126
  453. package/latticeai/setup/wizard/catalog.py +0 -172
  454. package/latticeai/setup/wizard/detect.py +0 -323
  455. package/latticeai/setup/wizard/install.py +0 -348
  456. package/latticeai/setup/wizard/paths.py +0 -168
  457. package/latticeai/setup/wizard/plans.py +0 -74
  458. package/latticeai/setup/wizard/recommend.py +0 -320
  459. package/scripts/bench_agent_smoke.py +0 -409
  460. package/scripts/bench_models.py +0 -540
  461. package/scripts/bench_vector_index.py +0 -295
  462. package/scripts/funnel_soft_gate.py +0 -192
  463. package/scripts/generate_agent_loop_fixtures.py +0 -907
  464. package/scripts/generate_rust_parity_fixtures.py +0 -1000
  465. package/scripts/measure_brain_home_fill.mjs +0 -141
  466. package/scripts/migrate_brain_storage.py +0 -57
  467. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  468. package/scripts/profile_kg.py +0 -355
  469. package/server.py +0 -30
  470. package/static/app/assets/arrow-left-BHYmOTWU.js +0 -1
  471. package/static/app/assets/circle-check-CLeWJr67.js +0 -1
  472. package/static/app/assets/index-DxmOfNRi.css +0 -2
  473. package/static/app/assets/search-BgSVX6NG.js +0 -1
@@ -1,614 +0,0 @@
1
- """Question-driven everyday automation intelligence (v9.4.0).
2
-
3
- Users repeat themselves: the same "오늘 뭐 해야 하지?", "프로젝트 상태 정리해줘",
4
- "그 폴더에 새 문서 뭐 들어왔어?" questions come back every day. This service
5
- watches for those patterns and turns them into one-click, consent-first
6
- automation suggestions:
7
-
8
- * **question_patterns** — mines the user's own chat history for recurring
9
- question intents. Clustering is deterministic and local (token-signature
10
- similarity, no model call), so the evidence shown to the user is their own
11
- literal questions.
12
- * **suggestions** — converts recurring patterns and active local knowledge
13
- sources (folders the user connected to the Brain) into concrete automation
14
- proposals: a scheduled answer for a recurring question, a digest for a busy
15
- knowledge folder, or one of the starter recipes when the intent matches.
16
- * **build_suggestion_workflow** — turns an accepted suggestion into a
17
- workflow definition using the same consent-first shape as the starter
18
- recipes: installed as a disabled draft, review-queue gated, local-only,
19
- no external actions.
20
- * **overview** — one payload for the automation surface: suggestions,
21
- installed automations with their enable state, and pattern evidence.
22
-
23
- Every suggestion id is deterministic, so install calls are idempotent and the
24
- UI can safely re-request suggestions without duplicates.
25
-
26
- v9.8.0 quality layer (additive): every suggestion carries a deterministic
27
- ``confidence`` score plus its ``confidence_factors`` evidence (repeat count,
28
- distinct phrasings, intent match, related Brain nodes / indexed files);
29
- suggestions below a minimum confidence are suppressed, duplicate suggestions
30
- targeting an already-suggested or already-installed starter recipe are
31
- deduplicated, and the response reports the suppression counters under
32
- ``quality``.
33
- """
34
-
35
- from __future__ import annotations
36
-
37
- import hashlib
38
- import logging
39
- import re
40
- from dataclasses import dataclass, field
41
- from typing import Any, Dict, List, Optional
42
-
43
- from latticeai.core.timeutil import now_iso as _now
44
-
45
- LOGGER = logging.getLogger(__name__)
46
-
47
- _MIN_PATTERN_COUNT = 2
48
- _MAX_HISTORY = 4000
49
- _SIGNATURE_SIMILARITY = 0.6
50
-
51
- # Suggestion quality gates (v9.8.0, additive). Suggestions below the minimum
52
- # confidence are suppressed (insufficient evidence); suggestions between the
53
- # minimum and the low-confidence threshold are shown but flagged so the UI
54
- # can render them less prominently.
55
- _MIN_SUGGESTION_CONFIDENCE = 0.35
56
- _LOW_CONFIDENCE_THRESHOLD = 0.5
57
- _KG_GROUNDING_LIMIT = 5
58
- #: Charged when the graph *was* consulted and found nothing related. v11.2.0
59
- #: fixes a dead policy: the floor above was unreachable for recurring questions
60
- #: (the cheapest possible question — asked twice, one phrasing, no recipe —
61
- #: scored 0.475), so the suppression branch could only ever be exercised by a
62
- #: test that moved the constant. Absence of grounding is real evidence, and
63
- #: charging for it is what makes the floor mean something again.
64
- _UNGROUNDED_PENALTY = 0.15
65
-
66
-
67
- def _question_confidence(
68
- count: int,
69
- examples: List[str],
70
- recipe_id: Optional[str],
71
- kg_related: Optional[int],
72
- ) -> tuple:
73
- """Deterministic confidence for a recurring-question suggestion.
74
-
75
- Evidence factors: how often the question repeats, how many distinct
76
- phrasings exist, whether it maps onto a known starter-recipe intent, and
77
- what the Brain knows about it.
78
-
79
- That last factor is three-valued on purpose. ``None`` means the graph could
80
- not be consulted — unknown, so nothing is added or taken away. A positive
81
- count is supporting evidence. **Zero** is not the same as unknown: the
82
- graph was asked, and it knows nothing about this question, which is a
83
- reason to be less sure rather than a reason to be neutral.
84
- """
85
- score = 0.3 + 0.5 * min(1.0, (int(count) - 1) / 4)
86
- score += min(0.15, 0.05 * len(examples or []))
87
- if recipe_id:
88
- score += 0.15
89
- if kg_related:
90
- score += min(0.2, 0.05 * int(kg_related))
91
- elif kg_related == 0:
92
- score -= _UNGROUNDED_PENALTY
93
- factors = {
94
- "repeat_count": int(count),
95
- "distinct_examples": len(examples or []),
96
- "intent_match": bool(recipe_id),
97
- "kg_related_nodes": kg_related,
98
- "kg_grounded": None if kg_related is None else bool(kg_related),
99
- }
100
- return round(max(0.0, min(1.0, score)), 2), factors
101
-
102
-
103
- def _source_confidence(indexed: int, watch_enabled: bool) -> tuple:
104
- """Deterministic confidence for a knowledge-source digest suggestion."""
105
- score = 0.25 + 0.6 * min(1.0, int(indexed) / 25)
106
- if watch_enabled:
107
- score += 0.1
108
- factors = {"indexed_files": int(indexed), "watch_enabled": bool(watch_enabled)}
109
- return round(min(1.0, score), 2), factors
110
-
111
- _QUESTION_HINT_RE = re.compile(
112
- r"(\?|어때|뭐야|뭐가|뭘까|알려줘|보여줘|정리해|요약해|정리 좀|요약 좀|해줘"
113
- r"|what|how|why|when|where|status|summar|remind|list|show me|tell me)",
114
- re.IGNORECASE,
115
- )
116
-
117
- _STOPWORDS = {
118
- "the", "a", "an", "is", "are", "was", "to", "of", "in", "on", "for", "and",
119
- "or", "me", "my", "you", "please", "can", "could", "would", "it", "this",
120
- "that", "do", "does", "what", "how", "about",
121
- "좀", "그", "이", "저", "것", "거", "게", "내", "제", "나", "너", "우리",
122
- "해줘", "해주세요", "주세요", "합니다", "있어", "있나", "있는지", "어떻게",
123
- "뭐야", "뭐가", "알려줘", "보여줘",
124
- }
125
-
126
- # Intent buckets map recurring question language onto the starter recipes.
127
- _INTENT_RULES = (
128
- (
129
- "digest",
130
- re.compile(r"(오늘|하루|today|daily).{0,12}(정리|요약|digest|summary|기억|메모)|"
131
- r"(정리|요약).{0,8}(해줘|해 줘|부탁)|summar(y|ize)", re.IGNORECASE),
132
- "daily-memory-digest",
133
- ),
134
- (
135
- "project_review",
136
- re.compile(r"(프로젝트|project|진행|progress|status|상태|주간|weekly|이번 주)", re.IGNORECASE),
137
- "weekly-project-review",
138
- ),
139
- (
140
- "follow_up",
141
- re.compile(r"(리마인드|remind|챙겨|잊지|deadline|마감|follow.?up|나중에|까먹)", re.IGNORECASE),
142
- "follow-up-radar",
143
- ),
144
- )
145
-
146
-
147
- def _tokens(text: str) -> List[str]:
148
- return [
149
- token
150
- for token in re.findall(r"[\w가-힣]+", str(text or "").lower())
151
- if len(token) > 1 and token not in _STOPWORDS
152
- ]
153
-
154
-
155
- def _signature(tokens: List[str]) -> frozenset:
156
- return frozenset(tokens)
157
-
158
-
159
- def _similarity(left: frozenset, right: frozenset) -> float:
160
- if not left or not right:
161
- return 0.0
162
- return len(left & right) / len(left | right)
163
-
164
-
165
- def _stable_id(prefix: str, seed: str) -> str:
166
- return f"{prefix}-{hashlib.sha256(seed.encode('utf-8')).hexdigest()[:10]}"
167
-
168
-
169
- @dataclass
170
- class _Pattern:
171
- representative: str
172
- signature: frozenset
173
- count: int = 1
174
- last_asked: str = ""
175
- intent: str = "recurring_question"
176
- recipe_id: Optional[str] = None
177
- examples: List[str] = field(default_factory=list)
178
-
179
- def as_dict(self) -> Dict[str, Any]:
180
- return {
181
- "id": _stable_id("pat", " ".join(sorted(self.signature))),
182
- "representative": self.representative[:160],
183
- "count": self.count,
184
- "last_asked": self.last_asked,
185
- "intent": self.intent,
186
- "recipe_id": self.recipe_id,
187
- "examples": self.examples[:3],
188
- }
189
-
190
-
191
- class AutomationIntelligenceService:
192
- def __init__(
193
- self,
194
- *,
195
- conversation_store: Any = None,
196
- knowledge_graph: Any = None,
197
- store: Any = None,
198
- enable_graph: bool = True,
199
- ) -> None:
200
- self._conversations = conversation_store
201
- self._kg = knowledge_graph
202
- self._store = store
203
- self._enable_graph = bool(enable_graph and knowledge_graph is not None)
204
-
205
- # ── question pattern mining ──────────────────────────────────────────
206
-
207
- def _user_questions(
208
- self, *, user_email: Optional[str], workspace_id: Optional[str]
209
- ) -> List[Dict[str, Any]]:
210
- if self._conversations is None:
211
- return []
212
- try:
213
- items = self._conversations.history(
214
- user_email=user_email,
215
- allowed_workspaces={workspace_id} if workspace_id is not None else None,
216
- include_legacy_global=workspace_id is None,
217
- limit=_MAX_HISTORY,
218
- )
219
- except Exception:
220
- LOGGER.exception("automation intelligence history read failed")
221
- return []
222
- questions = []
223
- for item in items:
224
- if item.get("role") != "user":
225
- continue
226
- content = str(item.get("content") or "").strip()
227
- if not content or len(content) < 6 or content.startswith("/"):
228
- continue
229
- if not _QUESTION_HINT_RE.search(content):
230
- continue
231
- questions.append({"content": content, "timestamp": str(item.get("timestamp") or "")})
232
- return questions
233
-
234
- def question_patterns(
235
- self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
236
- ) -> Dict[str, Any]:
237
- questions = self._user_questions(user_email=user_email, workspace_id=workspace_id)
238
- patterns: List[_Pattern] = []
239
- for question in questions:
240
- tokens = _tokens(question["content"])
241
- if len(tokens) < 2:
242
- continue
243
- signature = _signature(tokens)
244
- match = None
245
- for pattern in patterns:
246
- if _similarity(pattern.signature, signature) >= _SIGNATURE_SIMILARITY:
247
- match = pattern
248
- break
249
- if match is None:
250
- patterns.append(
251
- _Pattern(
252
- representative=question["content"],
253
- signature=signature,
254
- last_asked=question["timestamp"],
255
- examples=[question["content"]],
256
- )
257
- )
258
- continue
259
- match.count += 1
260
- # Union keeps the cluster stable as phrasing drifts.
261
- match.signature = match.signature | signature
262
- if question["timestamp"] >= match.last_asked:
263
- match.last_asked = question["timestamp"]
264
- match.representative = question["content"]
265
- if question["content"] not in match.examples:
266
- match.examples.append(question["content"])
267
-
268
- recurring = [p for p in patterns if p.count >= _MIN_PATTERN_COUNT]
269
- for pattern in recurring:
270
- for intent, rule, recipe_id in _INTENT_RULES:
271
- if rule.search(pattern.representative):
272
- pattern.intent = intent
273
- pattern.recipe_id = recipe_id
274
- break
275
-
276
- recurring.sort(key=lambda p: (p.count, p.last_asked), reverse=True)
277
- return {
278
- "questions_scanned": len(questions),
279
- "patterns": [p.as_dict() for p in recurring[:20]],
280
- "generated_at": _now(),
281
- }
282
-
283
- # ── knowledge sources ────────────────────────────────────────────────
284
-
285
- def _knowledge_sources(self) -> List[Dict[str, Any]]:
286
- if not self._enable_graph or not hasattr(self._kg, "local_sources"):
287
- return []
288
- try:
289
- return list(self._kg.local_sources().get("sources") or [])
290
- except Exception:
291
- LOGGER.exception("automation intelligence source read failed")
292
- return []
293
-
294
- def _kg_related_count(
295
- self, text: str, *, workspace_id: Optional[str]
296
- ) -> Optional[int]:
297
- """Count Brain nodes related to a recurring question (grounding evidence).
298
-
299
- Returns ``None`` when the graph is unavailable so confidence scoring can
300
- distinguish "grounding impossible" from "grounded with zero hits".
301
- Scoping mirrors the conversation-history read: an explicit workspace is
302
- strict, no workspace falls back to the unscoped/legacy view.
303
- """
304
- if not self._enable_graph or not hasattr(self._kg, "search"):
305
- return None
306
- try:
307
- report = self._kg.search(
308
- str(text or "")[:200],
309
- limit=_KG_GROUNDING_LIMIT,
310
- allowed_workspaces={workspace_id} if workspace_id is not None else None,
311
- include_legacy_global=workspace_id is None,
312
- )
313
- except Exception:
314
- LOGGER.exception("automation intelligence KG grounding failed")
315
- return None
316
- if not isinstance(report, dict):
317
- return None
318
- return len(report.get("matches") or [])
319
-
320
- # ── suggestions ──────────────────────────────────────────────────────
321
-
322
- def _installed_workflows(
323
- self, *, workspace_id: Optional[str]
324
- ) -> tuple:
325
- """Map installed automation workflows by suggestion id and recipe id.
326
-
327
- The recipe map lets a recurring-question suggestion that targets an
328
- already-installed starter recipe surface as *installed* instead of
329
- re-suggesting the same automation — the install API is idempotent on
330
- exactly this provenance, so the read side must agree.
331
- """
332
- by_suggestion: Dict[str, Dict[str, Any]] = {}
333
- by_recipe: Dict[str, Dict[str, Any]] = {}
334
- if self._store is None:
335
- return by_suggestion, by_recipe
336
- try:
337
- workflows = self._store.list_workflows(workspace_id=workspace_id).get("workflows") or []
338
- except Exception:
339
- LOGGER.exception("automation intelligence workflow read failed")
340
- return by_suggestion, by_recipe
341
- for workflow in workflows:
342
- metadata = (workflow or {}).get("metadata") or {}
343
- created_from = metadata.get("created_from")
344
- if created_from == "automation_suggestion" and metadata.get("suggestion_id"):
345
- by_suggestion[str(metadata["suggestion_id"])] = workflow
346
- elif created_from == "brain_automation_recipe" and metadata.get("recipe_id"):
347
- by_recipe[str(metadata["recipe_id"])] = workflow
348
- return by_suggestion, by_recipe
349
-
350
- def suggestions(
351
- self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
352
- ) -> Dict[str, Any]:
353
- pattern_report = self.question_patterns(user_email=user_email, workspace_id=workspace_id)
354
- by_suggestion, by_recipe = self._installed_workflows(workspace_id=workspace_id)
355
-
356
- items: List[Dict[str, Any]] = []
357
- suppressed_low_confidence = 0
358
- suppressed_duplicates = 0
359
- seen_recipe_ids: set = set()
360
-
361
- # Patterns arrive sorted by (count, last_asked) desc, so the first
362
- # suggestion per recipe is the strongest — later ones targeting the
363
- # same recipe would install the identical workflow and are suppressed.
364
- for pattern in pattern_report["patterns"]:
365
- recipe_id = pattern["recipe_id"]
366
- if recipe_id and recipe_id in seen_recipe_ids:
367
- suppressed_duplicates += 1
368
- continue
369
- kg_related = self._kg_related_count(
370
- pattern["representative"], workspace_id=workspace_id
371
- )
372
- confidence, factors = _question_confidence(
373
- pattern["count"], pattern["examples"], recipe_id, kg_related
374
- )
375
- if confidence < _MIN_SUGGESTION_CONFIDENCE:
376
- # Asked a couple of times, phrased one way, matching no recipe,
377
- # and the Brain knows nothing about it — not enough to propose
378
- # automating. Counted, so the surface can say how many were held
379
- # back rather than silently showing fewer.
380
- suppressed_low_confidence += 1
381
- continue
382
- if recipe_id:
383
- seen_recipe_ids.add(recipe_id)
384
- suggestion_id = _stable_id("sug-q", pattern["id"])
385
- workflow = by_suggestion.get(suggestion_id) or (
386
- by_recipe.get(str(recipe_id)) if recipe_id else None
387
- )
388
- items.append({
389
- "id": suggestion_id,
390
- "kind": "recurring_question",
391
- "intent": pattern["intent"],
392
- "recipe_id": recipe_id,
393
- "title": pattern["representative"],
394
- "reason": {
395
- "type": "repeated_question",
396
- "count": pattern["count"],
397
- "last_asked": pattern["last_asked"],
398
- "examples": pattern["examples"],
399
- },
400
- "cadence": "daily",
401
- "confidence": confidence,
402
- "confidence_factors": factors,
403
- "low_confidence": confidence < _LOW_CONFIDENCE_THRESHOLD,
404
- "installed": workflow is not None,
405
- "workflow_id": (workflow or {}).get("id"),
406
- })
407
-
408
- for source in self._knowledge_sources():
409
- file_status = source.get("file_status") or {}
410
- indexed = sum(int(v or 0) for v in file_status.values())
411
- if indexed <= 0:
412
- continue
413
- confidence, factors = _source_confidence(
414
- indexed, bool(source.get("watch_enabled"))
415
- )
416
- if confidence < _MIN_SUGGESTION_CONFIDENCE:
417
- # A barely-indexed folder is not enough evidence for a digest
418
- # automation yet — do not suggest.
419
- suppressed_low_confidence += 1
420
- continue
421
- suggestion_id = _stable_id("sug-src", str(source.get("id") or source.get("root_path") or ""))
422
- items.append({
423
- "id": suggestion_id,
424
- "kind": "knowledge_source",
425
- "intent": "source_digest",
426
- "recipe_id": None,
427
- "title": str(source.get("label") or source.get("root_path") or "knowledge folder"),
428
- "reason": {
429
- "type": "connected_source",
430
- "root_path": source.get("root_path"),
431
- "indexed_files": indexed,
432
- "watch_enabled": bool(source.get("watch_enabled")),
433
- },
434
- "cadence": "when new knowledge arrives",
435
- "confidence": confidence,
436
- "confidence_factors": factors,
437
- "low_confidence": confidence < _LOW_CONFIDENCE_THRESHOLD,
438
- "installed": suggestion_id in by_suggestion,
439
- "workflow_id": (by_suggestion.get(suggestion_id) or {}).get("id"),
440
- })
441
-
442
- return {
443
- "suggestions": items,
444
- "questions_scanned": pattern_report["questions_scanned"],
445
- "quality": {
446
- "min_confidence": _MIN_SUGGESTION_CONFIDENCE,
447
- "low_confidence_threshold": _LOW_CONFIDENCE_THRESHOLD,
448
- "suppressed_low_confidence": suppressed_low_confidence,
449
- "suppressed_duplicates": suppressed_duplicates,
450
- },
451
- "consent": {
452
- "default_state": "draft_disabled",
453
- "local_only": True,
454
- "external_actions": False,
455
- "requires_user_enable": True,
456
- "review_before_run": True,
457
- },
458
- "generated_at": _now(),
459
- }
460
-
461
- def find_suggestion(
462
- self,
463
- suggestion_id: str,
464
- *,
465
- user_email: Optional[str] = None,
466
- workspace_id: Optional[str] = None,
467
- ) -> Optional[Dict[str, Any]]:
468
- for suggestion in self.suggestions(user_email=user_email, workspace_id=workspace_id)["suggestions"]:
469
- if suggestion["id"] == suggestion_id:
470
- return suggestion
471
- return None
472
-
473
- # ── workflow building ────────────────────────────────────────────────
474
-
475
- def build_suggestion_workflow(
476
- self, suggestion: Dict[str, Any], *, enabled: bool = False
477
- ) -> Dict[str, Any]:
478
- """Build a consent-first workflow definition from a suggestion.
479
-
480
- Mirrors the starter-recipe shape: trigger → draft agent → review
481
- output, installed disabled by default, review-queue gated, local
482
- only, and stamped with provenance so installs stay idempotent.
483
- """
484
- kind = suggestion.get("kind")
485
- title = str(suggestion.get("title") or "automation")[:80]
486
- if kind == "knowledge_source":
487
- trigger: Dict[str, Any] = {"trigger": "brain_event"}
488
- trigger_name = "New knowledge in connected folder"
489
- name = f"Folder digest: {title}"
490
- reason = suggestion.get("reason") or {}
491
- prompt = (
492
- "New knowledge arrived from the connected folder "
493
- f"'{reason.get('root_path') or title}'. Draft a short digest of what "
494
- "was added, what changed, and any follow-ups it suggests. Use only "
495
- "the local Brain; do not contact external services."
496
- )
497
- creates = ["folder digest", "change summary", "follow-up suggestions"]
498
- else:
499
- trigger = {"trigger": "interval", "interval_seconds": 86_400}
500
- trigger_name = "User-enabled schedule"
501
- name = f"Scheduled answer: {title}"
502
- prompt = (
503
- "The user repeatedly asks this question:\n"
504
- f"“{suggestion.get('title')}”\n"
505
- "Answer it from the current Brain (memories, knowledge graph, "
506
- "recent activity) as a concise draft the user can review. "
507
- "Use only local knowledge; do not contact external services."
508
- )
509
- creates = ["scheduled answer draft", "supporting evidence", "suggested follow-ups"]
510
-
511
- trigger_config = {
512
- **trigger,
513
- "enabled": bool(enabled),
514
- "review_queue": True,
515
- "consent_required": True,
516
- "local_only": True,
517
- "external_actions": False,
518
- }
519
- return {
520
- "name": name,
521
- "nodes": [
522
- {
523
- "id": "trigger",
524
- "type": "trigger",
525
- "name": trigger_name,
526
- "config": trigger_config,
527
- "next": "draft",
528
- },
529
- {
530
- "id": "draft",
531
- "type": "agent",
532
- "name": "Draft for review",
533
- "config": {
534
- "agent": "agent:planner",
535
- "goal": prompt,
536
- "prompt": prompt,
537
- "roles": ["researcher", "planner", "executor", "reviewer"],
538
- "mode": "draft",
539
- "local_only": True,
540
- "external_actions": False,
541
- "requires_review": True,
542
- },
543
- "next": "output",
544
- },
545
- {
546
- "id": "output",
547
- "type": "output",
548
- "name": "Review before saving",
549
- "config": {
550
- "value": "Draft ready for review. Save, edit, or discard it before it becomes durable memory.",
551
- },
552
- "next": None,
553
- },
554
- ],
555
- "metadata": {
556
- "created_from": "automation_suggestion",
557
- "suggestion_id": suggestion.get("id"),
558
- "suggestion_kind": kind,
559
- "suggestion_title": title,
560
- "suggestion_reason": suggestion.get("reason"),
561
- "suggestion_confidence": suggestion.get("confidence"),
562
- "automation_state": "enabled" if enabled else "draft_disabled",
563
- "local_only": True,
564
- "external_actions": False,
565
- "requires_user_enable": not enabled,
566
- "creates": creates,
567
- },
568
- }
569
-
570
- # ── overview for the automation surface ──────────────────────────────
571
-
572
- def overview(
573
- self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
574
- ) -> Dict[str, Any]:
575
- suggestion_report = self.suggestions(user_email=user_email, workspace_id=workspace_id)
576
- installed: List[Dict[str, Any]] = []
577
- if self._store is not None:
578
- try:
579
- workflows = self._store.list_workflows(workspace_id=workspace_id).get("workflows") or []
580
- except Exception:
581
- LOGGER.exception("automation intelligence workflow read failed")
582
- workflows = []
583
- from latticeai.services.automation_execution import last_execution_view
584
-
585
- for workflow in workflows:
586
- metadata = (workflow or {}).get("metadata") or {}
587
- if metadata.get("created_from") not in {"automation_suggestion", "brain_automation_recipe"}:
588
- continue
589
- installed.append({
590
- "id": workflow.get("id"),
591
- "name": workflow.get("name"),
592
- "created_from": metadata.get("created_from"),
593
- "suggestion_id": metadata.get("suggestion_id"),
594
- "recipe_id": metadata.get("recipe_id"),
595
- "enabled": metadata.get("automation_state") == "enabled",
596
- "requires_user_enable": bool(metadata.get("requires_user_enable", True)),
597
- "creates": metadata.get("creates") or [],
598
- # Execution log surfacing (backlog #6): the newest of the
599
- # run-now stamp vs. the latest persisted workflow run.
600
- "last_execution": last_execution_view(
601
- workflow, store=self._store, workspace_id=workspace_id
602
- ),
603
- })
604
- return {
605
- "suggestions": suggestion_report["suggestions"],
606
- "questions_scanned": suggestion_report["questions_scanned"],
607
- "installed": installed,
608
- "quality": suggestion_report["quality"],
609
- "consent": suggestion_report["consent"],
610
- "generated_at": _now(),
611
- }
612
-
613
-
614
- __all__ = ["AutomationIntelligenceService"]