ltcai 11.5.2 → 11.7.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 (443) hide show
  1. package/README.md +93 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +119 -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/MULTI_AGENT_RUNTIME.md +12 -5
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -3
  10. package/docs/REALTIME_COLLABORATION.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/WORKFLOW_DESIGNER.md +3 -2
  14. package/docs/kg-schema.md +1 -1
  15. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  16. package/lattice_brain/__init__.py +42 -79
  17. package/lattice_brain/graph/__init__.py +9 -24
  18. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  19. package/lattice_brain/ingestion/__init__.py +19 -58
  20. package/lattice_brain/ingestion/pipeline.py +20 -398
  21. package/lattice_brain/multimodal/__init__.py +7 -18
  22. package/lattice_brain/multimodal/images.py +6 -264
  23. package/lattice_brain/multimodal/video.py +8 -247
  24. package/lattice_brain/runtime/__init__.py +8 -79
  25. package/lattice_brain/runtime/hooks.py +22 -584
  26. package/latticeai/__init__.py +1 -1
  27. package/latticeai/api/agent_worker_seam.py +7 -80
  28. package/latticeai/api/health.py +6 -20
  29. package/latticeai/api/local_files.py +16 -613
  30. package/latticeai/api/models.py +12 -87
  31. package/latticeai/api/search.py +17 -255
  32. package/latticeai/api/tools.py +69 -750
  33. package/latticeai/api/voice_capture.py +8 -70
  34. package/latticeai/api/worker_compute.py +842 -0
  35. package/latticeai/api/worker_seams.py +216 -0
  36. package/latticeai/app_factory.py +23 -232
  37. package/latticeai/cli/entrypoint.py +36 -249
  38. package/latticeai/core/agent_permission.py +16 -91
  39. package/latticeai/core/messages.py +89 -531
  40. package/latticeai/runtime/access_runtime.py +0 -14
  41. package/latticeai/runtime/bootstrap.py +10 -21
  42. package/latticeai/runtime/brain_runtime.py +35 -43
  43. package/latticeai/runtime/build_phases/__init__.py +21 -37
  44. package/latticeai/runtime/build_phases/features.py +99 -389
  45. package/latticeai/runtime/build_phases/foundation.py +82 -392
  46. package/latticeai/runtime/build_phases/web.py +91 -408
  47. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  48. package/latticeai/runtime/lifespan_runtime.py +7 -16
  49. package/latticeai/runtime/platform_services_runtime.py +1 -15
  50. package/latticeai/runtime/runtime_context.py +13 -130
  51. package/latticeai/runtime/security_runtime.py +10 -103
  52. package/latticeai/services/architecture_readiness.py +82 -43
  53. package/latticeai/services/model_runtime/__init__.py +2 -11
  54. package/latticeai/services/model_runtime/service.py +4 -28
  55. package/latticeai/services/p_reinforce.py +10 -261
  56. package/latticeai/services/product_readiness.py +31 -38
  57. package/latticeai/services/search_service.py +37 -795
  58. package/latticeai/services/tool_dispatch.py +30 -386
  59. package/latticeai/services/voice_capture.py +13 -107
  60. package/latticeai/tools/__init__.py +22 -49
  61. package/latticeai/tools/commands.py +0 -163
  62. package/latticeai/tools/computer.py +0 -39
  63. package/latticeai/tools/documents.py +1 -134
  64. package/latticeai/tools/filesystem.py +1 -247
  65. package/latticeai/tools/knowledge.py +1 -52
  66. package/latticeai/tools/local_files.py +0 -20
  67. package/latticeai/worker_app.py +75 -0
  68. package/package.json +2 -3
  69. package/requirements.txt +0 -5
  70. package/scripts/agent_eval.py +16 -28
  71. package/scripts/brain_quality_eval.py +20 -183
  72. package/scripts/bump_version.py +5 -5
  73. package/scripts/check_current_release_docs.mjs +7 -4
  74. package/scripts/check_openapi_drift.mjs +10 -0
  75. package/scripts/check_server_i18n.mjs +2 -18
  76. package/scripts/compose_openapi.py +377 -0
  77. package/scripts/export_openapi.py +32 -4
  78. package/scripts/gen_messages_catalog_fixture.py +302 -0
  79. package/scripts/gen_openapi_fragments.py +365 -0
  80. package/scripts/gen_redact_fixture.py +196 -0
  81. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  82. package/scripts/generate_agent_parity_fixtures.py +33 -14
  83. package/scripts/openapi_route_families.json +2161 -0
  84. package/scripts/release_screen_claims.json +54 -0
  85. package/scripts/run_integration_tests.mjs +134 -29
  86. package/scripts/run_sidecar_e2e.mjs +104 -11
  87. package/scripts/wheel_smoke.py +32 -22
  88. package/src-tauri/Cargo.lock +375 -9
  89. package/src-tauri/Cargo.toml +1 -1
  90. package/src-tauri/src/backend.rs +151 -45
  91. package/src-tauri/src/main.rs +18 -18
  92. package/src-tauri/src/topology.rs +58 -88
  93. package/src-tauri/tauri.conf.json +1 -2
  94. package/static/app/asset-manifest.json +41 -41
  95. package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
  96. package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
  100. package/static/app/assets/Capture-BsTokYkk.js +1 -0
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
  107. package/static/app/assets/System-CMHSO9qM.js +1 -0
  108. package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
  112. package/static/app/assets/circle-check-DruOxB-4.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
  119. package/static/app/assets/index-D9x-kSNy.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
  124. package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
  125. package/static/app/assets/search-CT9aho2j.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
  134. package/static/app/index.html +4 -4
  135. package/static/sw.js +1 -1
  136. package/lattice_brain/archive.py +0 -522
  137. package/lattice_brain/context.py +0 -325
  138. package/lattice_brain/conversations.py +0 -382
  139. package/lattice_brain/core.py +0 -82
  140. package/lattice_brain/graph/_kg_contract.py +0 -249
  141. package/lattice_brain/graph/curator.py +0 -676
  142. package/lattice_brain/graph/discovery.py +0 -595
  143. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  144. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  145. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  146. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  147. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  148. package/lattice_brain/graph/documents.py +0 -380
  149. package/lattice_brain/graph/fusion.py +0 -395
  150. package/lattice_brain/graph/identity.py +0 -175
  151. package/lattice_brain/graph/image_vectors.py +0 -230
  152. package/lattice_brain/graph/ingest.py +0 -829
  153. package/lattice_brain/graph/network.py +0 -205
  154. package/lattice_brain/graph/proactive.py +0 -724
  155. package/lattice_brain/graph/projection/__init__.py +0 -42
  156. package/lattice_brain/graph/projection/curation.py +0 -500
  157. package/lattice_brain/graph/projection/v2_schema.py +0 -518
  158. package/lattice_brain/graph/provenance.py +0 -524
  159. package/lattice_brain/graph/rerank.py +0 -163
  160. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  161. package/lattice_brain/graph/retrieval/context.py +0 -197
  162. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  163. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  164. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  165. package/lattice_brain/graph/retrieval/signals.py +0 -95
  166. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  167. package/lattice_brain/graph/retrieval_policy.py +0 -180
  168. package/lattice_brain/graph/retrieval_reads.py +0 -769
  169. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  170. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  171. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  172. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  173. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  174. package/lattice_brain/graph/schema.py +0 -792
  175. package/lattice_brain/graph/store.py +0 -268
  176. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  177. package/lattice_brain/graph/vector_index/base.py +0 -167
  178. package/lattice_brain/graph/vector_index/brute_force.py +0 -110
  179. package/lattice_brain/graph/vector_index/hnsw.py +0 -290
  180. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  181. package/lattice_brain/graph/vector_index/quantized.py +0 -148
  182. package/lattice_brain/graph/vector_index/selector.py +0 -161
  183. package/lattice_brain/graph/write_master.py +0 -308
  184. package/lattice_brain/ingestion/_contract.py +0 -90
  185. package/lattice_brain/ingestion/folder_scan.py +0 -57
  186. package/lattice_brain/ingestion/folders.py +0 -258
  187. package/lattice_brain/ingestion/jobs_api.py +0 -107
  188. package/lattice_brain/ingestion/routing.py +0 -295
  189. package/lattice_brain/ingestion_jobs.py +0 -380
  190. package/lattice_brain/memory.py +0 -75
  191. package/lattice_brain/portability/__init__.py +0 -90
  192. package/lattice_brain/portability/_contract.py +0 -42
  193. package/lattice_brain/portability/backups.py +0 -338
  194. package/lattice_brain/portability/bundles.py +0 -136
  195. package/lattice_brain/portability/constants.py +0 -93
  196. package/lattice_brain/portability/fsops.py +0 -138
  197. package/lattice_brain/portability/service.py +0 -41
  198. package/lattice_brain/portability/sharing.py +0 -710
  199. package/lattice_brain/quality.py +0 -543
  200. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  201. package/lattice_brain/runtime/agent_runtime.py +0 -859
  202. package/lattice_brain/runtime/contracts.py +0 -460
  203. package/lattice_brain/runtime/multi_agent.py +0 -942
  204. package/lattice_brain/runtime/statuses.py +0 -10
  205. package/lattice_brain/sealed_box.py +0 -240
  206. package/lattice_brain/self_model.py +0 -675
  207. package/lattice_brain/sensitivity.py +0 -94
  208. package/lattice_brain/storage/__init__.py +0 -22
  209. package/lattice_brain/storage/base.py +0 -100
  210. package/lattice_brain/storage/docker.py +0 -105
  211. package/lattice_brain/storage/factory.py +0 -31
  212. package/lattice_brain/storage/migration.py +0 -191
  213. package/lattice_brain/storage/postgres.py +0 -123
  214. package/lattice_brain/storage/sqlite.py +0 -143
  215. package/lattice_brain/synthesis.py +0 -824
  216. package/lattice_brain/workflow.py +0 -497
  217. package/latticeai/api/admin.py +0 -471
  218. package/latticeai/api/agent_registry.py +0 -105
  219. package/latticeai/api/agents.py +0 -228
  220. package/latticeai/api/auth.py +0 -383
  221. package/latticeai/api/automation_intelligence.py +0 -401
  222. package/latticeai/api/brain_intelligence.py +0 -199
  223. package/latticeai/api/browser.py +0 -493
  224. package/latticeai/api/change_proposals.py +0 -89
  225. package/latticeai/api/chat.py +0 -572
  226. package/latticeai/api/chat_agent_http.py +0 -892
  227. package/latticeai/api/chat_contracts.py +0 -73
  228. package/latticeai/api/chat_documents.py +0 -276
  229. package/latticeai/api/chat_helpers.py +0 -460
  230. package/latticeai/api/chat_history.py +0 -101
  231. package/latticeai/api/chat_hybrid.py +0 -113
  232. package/latticeai/api/chat_intents.py +0 -646
  233. package/latticeai/api/chat_stream.py +0 -216
  234. package/latticeai/api/chronicle.py +0 -63
  235. package/latticeai/api/command_center.py +0 -51
  236. package/latticeai/api/computer_use.py +0 -474
  237. package/latticeai/api/evidence_actions.py +0 -48
  238. package/latticeai/api/features.py +0 -70
  239. package/latticeai/api/funnel_metrics.py +0 -31
  240. package/latticeai/api/garden.py +0 -34
  241. package/latticeai/api/hooks.py +0 -165
  242. package/latticeai/api/index_jobs.py +0 -145
  243. package/latticeai/api/invitations.py +0 -100
  244. package/latticeai/api/knowledge_graph.py +0 -536
  245. package/latticeai/api/marketplace.py +0 -105
  246. package/latticeai/api/mcp.py +0 -482
  247. package/latticeai/api/memory.py +0 -270
  248. package/latticeai/api/network.py +0 -81
  249. package/latticeai/api/network_boundary.py +0 -225
  250. package/latticeai/api/permission_mode.py +0 -61
  251. package/latticeai/api/permissions.py +0 -436
  252. package/latticeai/api/plugins.py +0 -126
  253. package/latticeai/api/portability.py +0 -391
  254. package/latticeai/api/project_sessions.py +0 -114
  255. package/latticeai/api/realtime.py +0 -118
  256. package/latticeai/api/review_queue.py +0 -364
  257. package/latticeai/api/security_dashboard.py +0 -604
  258. package/latticeai/api/setup.py +0 -319
  259. package/latticeai/api/static_routes.py +0 -354
  260. package/latticeai/api/ui_redirects.py +0 -26
  261. package/latticeai/api/workflow_designer.py +0 -394
  262. package/latticeai/api/workspace.py +0 -856
  263. package/latticeai/api/workspace_scope.py +0 -125
  264. package/latticeai/core/agent/__init__.py +0 -93
  265. package/latticeai/core/agent/_contract.py +0 -79
  266. package/latticeai/core/agent/context.py +0 -57
  267. package/latticeai/core/agent/deps.py +0 -125
  268. package/latticeai/core/agent/execution.py +0 -622
  269. package/latticeai/core/agent/planning.py +0 -145
  270. package/latticeai/core/agent/recovery.py +0 -157
  271. package/latticeai/core/agent/runtime.py +0 -210
  272. package/latticeai/core/agent/verification.py +0 -231
  273. package/latticeai/core/agent_eval.py +0 -739
  274. package/latticeai/core/agent_helpers.py +0 -493
  275. package/latticeai/core/agent_profiles.py +0 -110
  276. package/latticeai/core/agent_prompts.py +0 -171
  277. package/latticeai/core/agent_registry.py +0 -232
  278. package/latticeai/core/agent_state.py +0 -41
  279. package/latticeai/core/agent_trace.py +0 -104
  280. package/latticeai/core/artifact_ledger.py +0 -109
  281. package/latticeai/core/audit.py +0 -260
  282. package/latticeai/core/builtin_hooks.py +0 -105
  283. package/latticeai/core/context_builder.py +0 -394
  284. package/latticeai/core/document_generator.py +0 -103
  285. package/latticeai/core/enterprise.py +0 -154
  286. package/latticeai/core/enterprise_admin.py +0 -158
  287. package/latticeai/core/file_generation/__init__.py +0 -115
  288. package/latticeai/core/file_generation/bundles.py +0 -76
  289. package/latticeai/core/file_generation/extraction.py +0 -154
  290. package/latticeai/core/file_generation/inference.py +0 -235
  291. package/latticeai/core/file_generation/orchestration.py +0 -152
  292. package/latticeai/core/file_generation/prompting.py +0 -117
  293. package/latticeai/core/file_generation/repair.py +0 -114
  294. package/latticeai/core/file_generation/sanitize.py +0 -61
  295. package/latticeai/core/file_generation/validation.py +0 -201
  296. package/latticeai/core/invitations.py +0 -132
  297. package/latticeai/core/legacy_compatibility.py +0 -243
  298. package/latticeai/core/logging_safety.py +0 -46
  299. package/latticeai/core/marketplace.py +0 -293
  300. package/latticeai/core/mcp_catalog.py +0 -452
  301. package/latticeai/core/mcp_registry.py +0 -506
  302. package/latticeai/core/network_boundary.py +0 -168
  303. package/latticeai/core/oidc.py +0 -208
  304. package/latticeai/core/plugins.py +0 -432
  305. package/latticeai/core/product_hardening.py +0 -218
  306. package/latticeai/core/project_sessions.py +0 -337
  307. package/latticeai/core/realtime.py +0 -238
  308. package/latticeai/core/run_explain.py +0 -426
  309. package/latticeai/core/run_store.py +0 -252
  310. package/latticeai/core/timezones.py +0 -80
  311. package/latticeai/core/workspace_computer_memory.py +0 -84
  312. package/latticeai/core/workspace_graph_trace.py +0 -155
  313. package/latticeai/core/workspace_indexing.py +0 -102
  314. package/latticeai/core/workspace_memory.py +0 -77
  315. package/latticeai/core/workspace_onboarding.py +0 -104
  316. package/latticeai/core/workspace_os.py +0 -978
  317. package/latticeai/core/workspace_os_constants.py +0 -126
  318. package/latticeai/core/workspace_os_state.py +0 -180
  319. package/latticeai/core/workspace_os_utils.py +0 -103
  320. package/latticeai/core/workspace_permissions.py +0 -101
  321. package/latticeai/core/workspace_plugins.py +0 -97
  322. package/latticeai/core/workspace_relationships.py +0 -99
  323. package/latticeai/core/workspace_reorganization.py +0 -335
  324. package/latticeai/core/workspace_review_items.py +0 -112
  325. package/latticeai/core/workspace_runs.py +0 -726
  326. package/latticeai/core/workspace_skills.py +0 -109
  327. package/latticeai/core/workspace_snapshots.py +0 -198
  328. package/latticeai/core/workspace_timeline.py +0 -110
  329. package/latticeai/integrations/__init__.py +0 -0
  330. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  331. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  332. package/latticeai/integrations/telegram_bot/config.py +0 -86
  333. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  334. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  335. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  336. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  337. package/latticeai/runtime/audit_runtime.py +0 -76
  338. package/latticeai/runtime/automation_runtime.py +0 -81
  339. package/latticeai/runtime/chat_wiring.py +0 -141
  340. package/latticeai/runtime/context_runtime.py +0 -66
  341. package/latticeai/runtime/feature_toggle_wiring.py +0 -157
  342. package/latticeai/runtime/history_runtime.py +0 -163
  343. package/latticeai/runtime/history_writer.py +0 -138
  344. package/latticeai/runtime/hooks_runtime.py +0 -77
  345. package/latticeai/runtime/model_wiring.py +0 -68
  346. package/latticeai/runtime/namespace_runtime.py +0 -163
  347. package/latticeai/runtime/network_boundary_wiring.py +0 -117
  348. package/latticeai/runtime/network_config_runtime.py +0 -56
  349. package/latticeai/runtime/permission_mode_wiring.py +0 -112
  350. package/latticeai/runtime/persistence_runtime.py +0 -159
  351. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  352. package/latticeai/runtime/review_wiring.py +0 -42
  353. package/latticeai/runtime/router_registration.py +0 -693
  354. package/latticeai/runtime/service_singletons.py +0 -55
  355. package/latticeai/runtime/sso_config_runtime.py +0 -128
  356. package/latticeai/runtime/user_key_runtime.py +0 -106
  357. package/latticeai/runtime/web_runtime.py +0 -92
  358. package/latticeai/server_app.py +0 -51
  359. package/latticeai/services/app_context.py +0 -130
  360. package/latticeai/services/automation_execution.py +0 -266
  361. package/latticeai/services/automation_intelligence.py +0 -614
  362. package/latticeai/services/brain_automation.py +0 -191
  363. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  364. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  365. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  366. package/latticeai/services/brain_intelligence/constants.py +0 -47
  367. package/latticeai/services/brain_intelligence/digest.py +0 -258
  368. package/latticeai/services/brain_intelligence/health.py +0 -331
  369. package/latticeai/services/brain_intelligence/proposals.py +0 -259
  370. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  371. package/latticeai/services/brain_intelligence/service.py +0 -48
  372. package/latticeai/services/change_proposals.py +0 -471
  373. package/latticeai/services/chat_service.py +0 -243
  374. package/latticeai/services/chronicle.py +0 -555
  375. package/latticeai/services/cloud_egress_audit.py +0 -85
  376. package/latticeai/services/cloud_extraction.py +0 -129
  377. package/latticeai/services/cloud_streaming.py +0 -268
  378. package/latticeai/services/cloud_token_guard.py +0 -84
  379. package/latticeai/services/command_center.py +0 -548
  380. package/latticeai/services/evidence_actions.py +0 -258
  381. package/latticeai/services/feature_toggles.py +0 -502
  382. package/latticeai/services/folder_watch.py +0 -520
  383. package/latticeai/services/funnel_metrics.py +0 -307
  384. package/latticeai/services/hybrid_chat.py +0 -316
  385. package/latticeai/services/hybrid_context.py +0 -228
  386. package/latticeai/services/hybrid_policy.py +0 -129
  387. package/latticeai/services/interop_bridges.py +0 -978
  388. package/latticeai/services/local_knowledge.py +0 -465
  389. package/latticeai/services/memory_service/__init__.py +0 -52
  390. package/latticeai/services/memory_service/_contract.py +0 -100
  391. package/latticeai/services/memory_service/brief.py +0 -431
  392. package/latticeai/services/memory_service/constants.py +0 -57
  393. package/latticeai/services/memory_service/maintenance.py +0 -138
  394. package/latticeai/services/memory_service/manager.py +0 -186
  395. package/latticeai/services/memory_service/proof.py +0 -136
  396. package/latticeai/services/memory_service/recall.py +0 -225
  397. package/latticeai/services/memory_service/service.py +0 -48
  398. package/latticeai/services/memory_service/stores.py +0 -110
  399. package/latticeai/services/mode_store.py +0 -132
  400. package/latticeai/services/model_recommendation.py +0 -224
  401. package/latticeai/services/model_runtime/cloud.py +0 -87
  402. package/latticeai/services/network_boundary_service.py +0 -117
  403. package/latticeai/services/obsidian_bridge.py +0 -609
  404. package/latticeai/services/openai_compatible_adapter.py +0 -101
  405. package/latticeai/services/permission_mode_service.py +0 -122
  406. package/latticeai/services/platform_runtime.py +0 -366
  407. package/latticeai/services/review_queue.py +0 -380
  408. package/latticeai/services/router_context.py +0 -59
  409. package/latticeai/services/run_executor.py +0 -387
  410. package/latticeai/services/self_model_service.py +0 -171
  411. package/latticeai/services/setup_detection.py +0 -147
  412. package/latticeai/services/triggers.py +0 -378
  413. package/latticeai/services/upload_service.py +0 -172
  414. package/latticeai/services/workspace_service.py +0 -165
  415. package/latticeai/setup/__init__.py +0 -25
  416. package/latticeai/setup/auto_setup.py +0 -846
  417. package/latticeai/setup/demo_corpus.py +0 -98
  418. package/latticeai/setup/wizard/__init__.py +0 -126
  419. package/latticeai/setup/wizard/catalog.py +0 -175
  420. package/latticeai/setup/wizard/detect.py +0 -302
  421. package/latticeai/setup/wizard/install.py +0 -348
  422. package/latticeai/setup/wizard/paths.py +0 -165
  423. package/latticeai/setup/wizard/plans.py +0 -74
  424. package/latticeai/setup/wizard/recommend.py +0 -320
  425. package/scripts/bench_agent_smoke.py +0 -409
  426. package/scripts/bench_models.py +0 -540
  427. package/scripts/bench_vector_index.py +0 -295
  428. package/scripts/funnel_soft_gate.py +0 -192
  429. package/scripts/generate_agent_loop_fixtures.py +0 -994
  430. package/scripts/generate_rust_parity_fixtures.py +0 -908
  431. package/scripts/migrate_brain_storage.py +0 -57
  432. package/scripts/parity_fixture_corpus_context.py +0 -162
  433. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  434. package/scripts/profile_kg.py +0 -355
  435. package/server.py +0 -30
  436. package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
  437. package/static/app/assets/Capture-DiQ219jW.js +0 -1
  438. package/static/app/assets/System-CieofHQa.js +0 -1
  439. package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
  440. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  441. package/static/app/assets/index-DxmOfNRi.css +0 -2
  442. package/static/app/assets/primitives-SNp0LRJz.js +0 -1
  443. package/static/app/assets/search-BcHqkjoy.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"]