ltcai 11.5.2 → 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 (439) hide show
  1. package/README.md +91 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +75 -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 +21 -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-CToZnOHz.js} +1 -1
  96. package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
  100. package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
  107. package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
  108. package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
  112. package/static/app/assets/circle-check-CY20XMCh.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
  119. package/static/app/assets/index-BovSWRmQ.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
  124. package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
  125. package/static/app/assets/search-DTxtjQ07.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.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/arrow-left-kfsrk0mv.js +0 -1
  437. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  438. package/static/app/assets/index-DxmOfNRi.css +0 -2
  439. package/static/app/assets/search-BcHqkjoy.js +0 -1
@@ -1,859 +0,0 @@
1
- """AgentRuntime — the single boundary for agent execution and observability.
2
-
3
- (lattice_brain/runtime/agent_runtime.py)
4
- 책임: 퍼사드. store/orchestrator/hooks/audit 주입 받아 start/reserve/complete,
5
- status/health/config/events/replay/stop, pre/post_run hook firing.
6
- RunExecutor와 /agents 라우터의 유일한 의존 대상.
7
- 의존: .multi_agent (orchestrator), .hooks, store (WORKSPACE_OS).
8
- 진입점: app_factory.py:AGENT_RUNTIME (wiring root), api/agents.py, RunExecutor.
9
-
10
- Before this module the agent concern was spread across three places: the
11
- :class:`~lattice_brain.runtime.multi_agent.MultiAgentOrchestrator` (role pipeline),
12
- the :class:`~latticeai.services.platform_runtime.PlatformRuntime` (cross-system
13
- wiring + an ad-hoc ``run_agent``), and ``api/agents.py`` (HTTP transport that
14
- also owned orchestration + persistence + audit). The frontend reached past all
15
- of them into the workspace store via ``/workspace/agents``.
16
-
17
- ``AgentRuntime`` collapses that into one façade with a small, stable surface:
18
-
19
- * **configuration** — :meth:`config`, :meth:`roles`
20
- * **status / health** — :meth:`status`, :meth:`health`
21
- * **execution** — :meth:`start`, :meth:`stop`
22
- * **events / state** — :meth:`list_runs`, :meth:`get_run`, :meth:`events`, :meth:`replay`
23
-
24
- It *wraps* the existing orchestrator and run store rather than reimplementing
25
- them — execution semantics are unchanged, but every caller (HTTP router and, via
26
- it, the frontend) now depends on this boundary instead of internal paths.
27
- """
28
-
29
- from __future__ import annotations
30
-
31
- from typing import Any, Callable, Dict, List, Optional
32
-
33
- from ..quiet import quiet
34
- from ..utils import now_iso as _now
35
- from .contracts import (
36
- contract_view,
37
- contract_views,
38
- extract_contract,
39
- multi_agent_contract,
40
- run_record_contract,
41
- runtime_boundary_contract,
42
- )
43
- from .multi_agent import (
44
- AGENT_ROLES,
45
- CORE_PIPELINE,
46
- MULTI_AGENT_VERSION,
47
- ROLE_AGENT_IDS,
48
- )
49
- from .statuses import (
50
- RUN_ACTIVE_STATUSES as _ACTIVE_STATUSES,
51
- )
52
- from .statuses import (
53
- RUN_TERMINAL_STATUSES as _TERMINAL_STATUSES,
54
- )
55
-
56
- ROLE_DESCRIPTIONS = {
57
- "researcher": "Gathers workspace context and memory for the goal.",
58
- "planner": "Decomposes the goal into an ordered, bounded plan.",
59
- "executor": "Executes each planned step, invoking tools and workflows.",
60
- "reviewer": "Reviews the executed work and approves, rejects, or retries.",
61
- "release": "Finalizes and summarizes the approved outcome.",
62
- }
63
-
64
- # Run statuses the orchestrator can emit that mean "still working". The default
65
- # orchestrator runs synchronously, so persisted runs are always terminal; this
66
- # set lets the runtime report live work if a future async runner lands.
67
- def _compact_text(value: Any, *, limit: int) -> str:
68
- return " ".join(str(value or "").split())[:limit]
69
-
70
-
71
- class AgentRuntimeUnavailable(RuntimeError):
72
- """Raised when a product run would otherwise persist simulation output."""
73
-
74
-
75
- class AgentRuntime:
76
- def __init__(
77
- self,
78
- *,
79
- store: Any,
80
- orchestrator_factory: Callable[[Optional[str], Optional[str]], Any],
81
- workspace_graph: Callable[[], Any],
82
- append_audit_event: Callable[..., None],
83
- max_retries_cap: int = 5,
84
- hooks: Any = None,
85
- allow_simulation_runs: bool = False,
86
- memory_ingest: Optional[Callable[..., Dict[str, Any]]] = None,
87
- review_sink: Any = None,
88
- ):
89
- self._store = store
90
- self._orchestrator_factory = orchestrator_factory
91
- self._workspace_graph = workspace_graph
92
- self._append_audit_event = append_audit_event
93
- self._max_retries_cap = int(max_retries_cap)
94
- # Lifecycle hooks registry (optional). When present, ``start`` fires the
95
- # ``pre_run`` / ``post_run`` hooks; a blocking ``pre_run`` aborts the run.
96
- self._hooks = hooks
97
- self._allow_simulation_runs = bool(allow_simulation_runs)
98
- self._run_executor: Any = None
99
- # Optional memory synthesis: successful agent runs produce durable
100
- # Brain memories (long_term / workspace tier) so users *feel* the results
101
- # in BrainBrief, MemoryRings, search and graph immediately.
102
- self._memory_ingest = memory_ingest
103
- self._review_sink = review_sink
104
-
105
- def attach_executor(self, executor: Any) -> None:
106
- self._run_executor = executor
107
-
108
- def _execution_mode(self) -> str:
109
- return "async" if self._run_executor is not None else "synchronous"
110
-
111
- def boundary(self) -> Dict[str, Any]:
112
- return runtime_boundary_contract(
113
- name="AgentRuntime",
114
- runtime="multi_agent",
115
- entrypoint="lattice_brain.runtime.agent_runtime.AgentRuntime",
116
- surface="/agents",
117
- owns="product agent execution, observability, status, health, events, replay, and stop",
118
- )
119
-
120
- # ── configuration ─────────────────────────────────────────────────────
121
- def config(self) -> Dict[str, Any]:
122
- return {
123
- "version": MULTI_AGENT_VERSION,
124
- "boundary": self.boundary(),
125
- "roles": list(AGENT_ROLES),
126
- "default_pipeline": list(CORE_PIPELINE),
127
- "max_retries_cap": self._max_retries_cap,
128
- "execution_mode": self._execution_mode(),
129
- "simulation_runs_allowed": self._allow_simulation_runs,
130
- "cancellation": (
131
- "cooperative; running synchronous model/tool calls finish their current step before a cancelled status is persisted"
132
- if self._run_executor is not None else
133
- "not supported for the synchronous runtime"
134
- ),
135
- }
136
-
137
- def roles(self) -> List[Dict[str, Any]]:
138
- return [
139
- {
140
- "role": role,
141
- "agent_id": ROLE_AGENT_IDS.get(role, f"agent:{role}"),
142
- "description": ROLE_DESCRIPTIONS.get(role, ""),
143
- "terminal": role not in {"researcher", "planner", "executor", "reviewer"},
144
- }
145
- for role in AGENT_ROLES
146
- ]
147
-
148
- # ── health ────────────────────────────────────────────────────────────
149
- def health(self) -> Dict[str, Any]:
150
- checks: Dict[str, Any] = {}
151
- ok = True
152
- ready = True
153
- try:
154
- self._store.list_agents(workspace_id=None)
155
- checks["run_store"] = {"status": "ok"}
156
- except Exception as exc: # pragma: no cover - defensive
157
- ok = False
158
- checks["run_store"] = {"status": "error", "detail": str(exc)}
159
- try:
160
- orchestrator = self._orchestrator_factory(None, None)
161
- mode = getattr(orchestrator, "mode", "simulation")
162
- if mode == "simulation":
163
- if self._allow_simulation_runs:
164
- checks["orchestrator"] = {
165
- "status": "ok",
166
- "mode": mode,
167
- "detail": "Simulation runs are explicitly enabled for this non-product runtime.",
168
- }
169
- else:
170
- ready = False
171
- checks["orchestrator"] = {
172
- "status": "unavailable",
173
- "mode": mode,
174
- "detail": "No LLM-backed model is loaded; product execution API refuses simulation runs.",
175
- }
176
- else:
177
- checks["orchestrator"] = {"status": "ok", "mode": mode}
178
- except Exception as exc: # pragma: no cover - defensive
179
- ok = False
180
- checks["orchestrator"] = {"status": "error", "detail": str(exc)}
181
- return {
182
- "status": "ok" if ok and ready else "unavailable" if ok else "degraded",
183
- "ready": bool(ok and ready),
184
- "checks": checks,
185
- }
186
-
187
- def _live_orchestrator(self, user_email: Optional[str], scope: Optional[str]) -> Any:
188
- orchestrator = self._orchestrator_factory(user_email or None, scope)
189
- mode = getattr(orchestrator, "mode", "simulation")
190
- if mode == "simulation" and not self._allow_simulation_runs:
191
- raise AgentRuntimeUnavailable(
192
- "Agent execution is unavailable because no LLM-backed model is loaded. "
193
- "Simulation mode is disabled in the product execution API so it cannot be recorded as real success."
194
- )
195
- return orchestrator
196
-
197
- # ── roster + status ───────────────────────────────────────────────────
198
- def _roster(self, runs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
199
- """Canonical role roster enriched with real run statistics."""
200
- by_agent: Dict[str, Dict[str, Any]] = {}
201
- for run in runs:
202
- aid = str(run.get("agent_id") or "")
203
- entry = by_agent.setdefault(aid, {"runs": 0, "last_status": None, "last_at": None})
204
- entry["runs"] += 1
205
- if entry["last_at"] is None: # runs are newest-first
206
- entry["last_status"] = run.get("status")
207
- entry["last_at"] = run.get("created_at") or run.get("completed_at")
208
-
209
- roster: List[Dict[str, Any]] = []
210
- order = list(CORE_PIPELINE) # planner, executor, reviewer first
211
- ordered_roles = order + [r for r in AGENT_ROLES if r not in order]
212
- for role in ordered_roles:
213
- agent_id = ROLE_AGENT_IDS.get(role, f"agent:{role}")
214
- stats = by_agent.get(agent_id, {"runs": 0, "last_status": None, "last_at": None})
215
- handoffs = []
216
- if role == "planner":
217
- handoffs = [ROLE_AGENT_IDS["executor"]]
218
- elif role == "executor":
219
- handoffs = [ROLE_AGENT_IDS["reviewer"]]
220
- roster.append({
221
- "id": agent_id,
222
- "name": role.capitalize(),
223
- "role": ROLE_DESCRIPTIONS.get(role, ""),
224
- "state": "available" if role != "release" else "idle",
225
- "runs": stats["runs"],
226
- "last_status": stats["last_status"],
227
- "last_at": stats["last_at"],
228
- "handoffs": handoffs,
229
- })
230
- return roster
231
-
232
- def status(self, *, scope: Optional[str] = None) -> Dict[str, Any]:
233
- try:
234
- listing = self._store.list_agents(workspace_id=scope)
235
- except Exception as exc: # pragma: no cover - defensive
236
- listing = {"agents": [], "runs": [], "error": str(exc)}
237
- runs = list(listing.get("runs") or [])
238
- active = sum(1 for r in runs if str(r.get("status")) in _ACTIVE_STATUSES)
239
- health = self.health()
240
- orchestrator_status = (health.get("checks") or {}).get("orchestrator") or {}
241
- ready = bool(health.get("ready"))
242
- return {
243
- "runtime": {
244
- "ready": ready,
245
- "version": MULTI_AGENT_VERSION,
246
- "execution_mode": self._execution_mode(),
247
- "mode": orchestrator_status.get("mode", "unknown"),
248
- "unavailable_reason": None if ready else orchestrator_status.get("detail"),
249
- "default_pipeline": list(CORE_PIPELINE),
250
- "total_runs": len(runs),
251
- "active_runs": active,
252
- },
253
- "health": health,
254
- "roles": self.roles(),
255
- "agents": self._roster(runs),
256
- "runs": runs[:25],
257
- "contracts": contract_views(runs[:25]),
258
- }
259
-
260
- def preview(
261
- self,
262
- goal: str,
263
- *,
264
- roles: Optional[List[str]] = None,
265
- inputs: Optional[Dict[str, Any]] = None,
266
- max_retries: int = 2,
267
- scope: Optional[str] = None,
268
- ) -> Dict[str, Any]:
269
- """Return the execution contract without reserving or starting a run.
270
-
271
- The preview is the product-facing readiness gate for agent execution:
272
- clients can show whether a real LLM-backed run can start, which roles
273
- will execute, how retry limits are clamped, and why the run is blocked.
274
- """
275
-
276
- requested_roles = list(roles or CORE_PIPELINE)
277
- unknown_roles = [role for role in requested_roles if role not in AGENT_ROLES]
278
- health = self.health()
279
- goal_ready = bool(str(goal or "").strip())
280
- retry_budget = self._clamp_retries(max_retries)
281
- blocking_reasons: List[str] = []
282
- if not goal_ready:
283
- blocking_reasons.append("goal is required")
284
- if unknown_roles:
285
- blocking_reasons.append(f"unknown roles: {', '.join(unknown_roles)}")
286
- if not health.get("ready"):
287
- orchestrator = (health.get("checks") or {}).get("orchestrator") or {}
288
- blocking_reasons.append(str(orchestrator.get("detail") or "agent runtime is unavailable"))
289
- can_start = not blocking_reasons
290
- return {
291
- "ready": can_start,
292
- "can_start": can_start,
293
- "blocking_reasons": blocking_reasons,
294
- "goal": str(goal or "").strip(),
295
- "roles": requested_roles,
296
- "unknown_roles": unknown_roles,
297
- "inputs_keys": sorted((inputs or {}).keys()),
298
- "max_retries": retry_budget,
299
- "max_retries_requested": max_retries,
300
- "scope": scope,
301
- "execution_mode": self._execution_mode(),
302
- "runtime": {
303
- "version": MULTI_AGENT_VERSION,
304
- "default_pipeline": list(CORE_PIPELINE),
305
- "max_retries_cap": self._max_retries_cap,
306
- "simulation_runs_allowed": self._allow_simulation_runs,
307
- },
308
- "health": health,
309
- }
310
-
311
- # ── events / state ────────────────────────────────────────────────────
312
- def list_runs(self, *, scope: Optional[str] = None) -> Dict[str, Any]:
313
- listing = self._store.list_agents(workspace_id=scope)
314
- runs = list(listing.get("runs") or [])
315
- payload = dict(listing)
316
- payload["contracts"] = contract_views(runs)
317
- return payload
318
-
319
- def get_run(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
320
- run = self._store.get_agent_run(run_id, workspace_id=scope)
321
- payload = {"run": run}
322
- contract = self._ensure_contract(run)
323
- if contract is not None:
324
- payload["contract"] = contract_view(contract)
325
- return payload
326
-
327
- def replay(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
328
- replay = self._store.replay_agent_run(run_id, workspace_id=scope)
329
- payload = {"replay": replay}
330
- contract = extract_contract(replay)
331
- if contract is not None:
332
- payload["contract"] = contract_view(contract)
333
- return payload
334
-
335
- def events(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
336
- run = self._store.get_agent_run(run_id, workspace_id=scope)
337
- status = str(run.get("status") or "")
338
- contract = self._ensure_contract(run)
339
- return {
340
- "run_id": run_id,
341
- "status": status,
342
- "is_final": status in _TERMINAL_STATUSES or status not in _ACTIVE_STATUSES,
343
- "current_role": run.get("current_role"),
344
- "timeline": run.get("timeline") or [],
345
- "handoffs": run.get("handoffs") or [],
346
- "contract": contract_view(contract) if contract is not None else None,
347
- }
348
-
349
- # ── execution ─────────────────────────────────────────────────────────
350
- def _fire_pre_run(
351
- self,
352
- *,
353
- goal: str,
354
- roles: Optional[List[str]],
355
- max_retries: int,
356
- user_email: Optional[str],
357
- scope: Optional[str],
358
- ) -> Optional[Dict[str, Any]]:
359
- pre_dispatch: Optional[Dict[str, Any]] = None
360
- if self._hooks is not None:
361
- pre_dispatch = self._hooks.fire_hook(
362
- "pre_run", "agent.run",
363
- payload={"goal": goal, "roles": roles or None, "max_retries": max_retries},
364
- user_email=user_email, workspace_id=scope,
365
- )
366
- if pre_dispatch.get("blocked"):
367
- self._append_audit_event(
368
- "multi_agent_run_blocked",
369
- user_email=user_email,
370
- reason=pre_dispatch.get("block_reason"),
371
- )
372
- raise PermissionError(pre_dispatch.get("block_reason") or "Agent run blocked by a pre_run hook.")
373
- return pre_dispatch
374
-
375
- def _clamp_retries(self, max_retries: int) -> int:
376
- return max(0, min(int(max_retries or 0), self._max_retries_cap))
377
-
378
- def _validate_roles(self, roles: Optional[List[str]]) -> Optional[List[str]]:
379
- """Reject unknown roles at the boundary instead of deep in orchestration.
380
-
381
- ``preview`` reports unknown roles as a blocking reason; execution paths
382
- must enforce the same contract so a run can never be recorded with a
383
- role the runtime does not own.
384
- """
385
- if not roles:
386
- return None
387
- unknown = [role for role in roles if role not in AGENT_ROLES]
388
- if unknown:
389
- raise ValueError(f"unknown roles: {', '.join(unknown)}")
390
- return list(roles)
391
-
392
- @staticmethod
393
- def _ensure_contract(record: Any) -> Optional[Dict[str, Any]]:
394
- """Return the record's family contract, synthesizing one for legacy rows.
395
-
396
- Every read surface (get_run/events/replay) must expose the
397
- ``agent-run-contract/v1`` envelope even for runs persisted before the
398
- contract family existed, so consumers never need a legacy branch.
399
- """
400
- contract = extract_contract(record)
401
- if contract is not None:
402
- return contract
403
- if isinstance(record, dict) and record.get("id"):
404
- return run_record_contract(record)
405
- return None
406
-
407
- @staticmethod
408
- def _result_patch(result: Any, goal: str) -> Dict[str, Any]:
409
- return {
410
- "agent_id": result.agent_id,
411
- "status": result.status,
412
- "input": goal,
413
- "output_text": result.output,
414
- "timeline": result.timeline,
415
- "relationships": [ROLE_AGENT_IDS.get(r, f"agent:{r}") for r in result.roles_run],
416
- "handoffs": result.handoffs,
417
- "context_packets": result.context_packets,
418
- "plan": result.plan,
419
- "plan_review": result.plan_review,
420
- "review_history": result.review_history,
421
- "retry_history": result.retry_history,
422
- "memory_snapshots": result.memory_snapshots,
423
- "mode": getattr(result, "mode", "simulation"),
424
- "contract": multi_agent_contract(result=result, goal=goal),
425
- "current_role": None,
426
- }
427
-
428
- def _synthesize_brain_memory(self, *, goal: str, result: Any, user_email: Optional[str], scope: Optional[str]) -> None:
429
- """Turn a successful agent run into durable Brain memory + graph nodes.
430
-
431
- This is the key to users *strongly feeling* the scale of agent work:
432
- delegated goals don't disappear into Act tab; they become part of the
433
- Living Brain (searchable, visible in rings/brief, connected in graph).
434
- """
435
- if not self._memory_ingest:
436
- return
437
- if getattr(result, "status", None) not in ("ok", "retried_ok"):
438
- return
439
- try:
440
- output = _compact_text(getattr(result, "output", ""), limit=2200)
441
- plan_steps = getattr(result, "plan", None) or []
442
- sections = self._agent_synthesis_sections(goal=goal, output=output, plan_steps=plan_steps, result=result)
443
- plan_summary = "; ".join(sections["plan_steps"][:4])
444
- content = "\n\n".join([
445
- f"[Agent synthesis] Goal: {_compact_text(goal, limit=240)}",
446
- f"Outcome: {output}",
447
- self._format_synthesis_section("Key facts", sections["facts"]),
448
- self._format_synthesis_section("Decisions", sections["decisions"]),
449
- self._format_synthesis_section("Follow-ups", sections["followups"]),
450
- f"Plan: {plan_summary}" if plan_summary else "",
451
- ]).strip()
452
- if not content or len(content) < 20:
453
- return # pragma: no cover — the "[Agent synthesis] Goal: " prefix is always >= 20 chars
454
- tags = ["agent-synthesis", "delegated", "auto"]
455
- self._memory_ingest(
456
- kind="long_term",
457
- content=content,
458
- user_email=user_email,
459
- tags=tags,
460
- metadata={
461
- "source": "agent_runtime",
462
- "synthesis_version": 2,
463
- "goal": _compact_text(goal, limit=200),
464
- "roles": getattr(result, "roles_run", None),
465
- "facts": sections["facts"],
466
- "decisions": sections["decisions"],
467
- "followups": sections["followups"],
468
- },
469
- graph=self._workspace_graph() if hasattr(self, "_workspace_graph") else None,
470
- workspace_id=scope,
471
- )
472
- if sections["decisions"]:
473
- self._memory_ingest(
474
- kind="decisions",
475
- content=f"Agent decision for {_compact_text(goal, limit=120)}: {'; '.join(sections['decisions'][:3])}",
476
- user_email=user_email,
477
- tags=["agent", "outcome", "decision"],
478
- metadata={"source": "agent_runtime_synthesis", "synthesis_version": 2, "goal": _compact_text(goal, limit=200)},
479
- workspace_id=scope,
480
- )
481
- if sections["followups"]:
482
- self._memory_ingest(
483
- kind="workspace",
484
- content=f"Agent follow-ups for {_compact_text(goal, limit=120)}: {'; '.join(sections['followups'][:5])}",
485
- user_email=user_email,
486
- tags=["agent", "follow-up", "next-action"],
487
- metadata={"source": "agent_runtime_followups", "synthesis_version": 2, "goal": _compact_text(goal, limit=200)},
488
- workspace_id=scope,
489
- )
490
- self._enqueue_agent_followups(
491
- goal=goal,
492
- followups=sections["followups"],
493
- result=result,
494
- user_email=user_email,
495
- scope=scope,
496
- output=output,
497
- )
498
- except Exception:
499
- # Synthesis must never break the run record.
500
- quiet()
501
-
502
- @staticmethod
503
- def _format_synthesis_section(title: str, items: List[str]) -> str:
504
- if not items:
505
- return ""
506
- return f"{title}:\n" + "\n".join(f"- {item}" for item in items[:5])
507
-
508
- @staticmethod
509
- def _agent_synthesis_sections(*, goal: str, output: str, plan_steps: List[Dict[str, Any]], result: Any) -> Dict[str, List[str]]:
510
- plan_descriptions = [
511
- _compact_text(step.get("description") or step.get("name") or step.get("id"), limit=120)
512
- for step in plan_steps
513
- if isinstance(step, dict) and (step.get("description") or step.get("name") or step.get("id"))
514
- ]
515
- sentences = [
516
- sentence.strip(" -•\t")
517
- for sentence in output.replace("\n", ". ").split(".")
518
- if sentence.strip(" -•\t")
519
- ]
520
- facts = [_compact_text(sentence, limit=180) for sentence in sentences[:4]]
521
- # `or {}` rather than the isinstance ternary: the ternary could still
522
- # yield None when the attribute was a dict-typed None, and the next
523
- # line calls .get on both.
524
- review = getattr(result, "review", None)
525
- review = review if isinstance(review, dict) else {}
526
- plan_review = getattr(result, "plan_review", None)
527
- plan_review = plan_review if isinstance(plan_review, dict) else {}
528
- decision_seed = review.get("decision") or review.get("status") or plan_review.get("decision") or getattr(result, "status", "")
529
- decisions = [
530
- _compact_text(f"Run finished with {decision_seed}", limit=160),
531
- _compact_text(f"Goal accepted: {goal}", limit=180),
532
- ]
533
- followups = [
534
- item for item in plan_descriptions
535
- if any(token in item.lower() for token in ("next", "follow", "review", "verify", "test", "ship", "publish", "document", "implement"))
536
- ][:5]
537
- if not followups:
538
- followups = plan_descriptions[:3]
539
- return {
540
- "facts": [item for item in facts if item],
541
- "decisions": [item for item in decisions if item],
542
- "followups": [item for item in followups if item],
543
- "plan_steps": plan_descriptions,
544
- }
545
-
546
- def _enqueue_agent_followups(
547
- self,
548
- *,
549
- goal: str,
550
- followups: List[str],
551
- result: Any,
552
- user_email: Optional[str],
553
- scope: Optional[str],
554
- output: str,
555
- ) -> None:
556
- if self._review_sink is None:
557
- return
558
- for index, followup in enumerate(followups[:5], start=1):
559
- try:
560
- self._review_sink.create(
561
- title=_compact_text(followup, limit=96) or f"Agent follow-up {index}",
562
- summary=_compact_text(f"From goal: {goal}", limit=420),
563
- source="agent_followup",
564
- kind="task_draft",
565
- payload={
566
- "goal": _compact_text(goal, limit=300),
567
- "followup": _compact_text(followup, limit=300),
568
- "output_preview": _compact_text(output, limit=800),
569
- "roles": getattr(result, "roles_run", None),
570
- },
571
- provenance={
572
- "agent_id": getattr(result, "agent_id", ""),
573
- "source_detail": "agent_runtime_followup",
574
- "status": getattr(result, "status", ""),
575
- },
576
- user_email=user_email,
577
- workspace_id=scope,
578
- )
579
- except Exception:
580
- quiet()
581
- continue
582
-
583
- def _post_run_hooks(
584
- self,
585
- *,
586
- run_id: Optional[str],
587
- result: Any,
588
- user_email: Optional[str],
589
- scope: Optional[str],
590
- status: Optional[str] = None,
591
- ) -> Optional[Dict[str, Any]]:
592
- if self._hooks is None:
593
- return None
594
- return self._hooks.fire_hook(
595
- "post_run", "agent.run",
596
- payload={
597
- "run_id": run_id,
598
- "agent_id": result.agent_id,
599
- "status": status or result.status,
600
- "retries": result.retries,
601
- },
602
- user_email=user_email, workspace_id=scope,
603
- )
604
-
605
- def reserve_run(
606
- self,
607
- goal: str,
608
- *,
609
- user_email: Optional[str],
610
- scope: Optional[str],
611
- roles: Optional[List[str]] = None,
612
- inputs: Optional[Dict[str, Any]] = None,
613
- max_retries: int = 2,
614
- ) -> Dict[str, Any]:
615
- """Create the durable queued row used by the async executor."""
616
- if not str(goal or "").strip():
617
- raise ValueError("goal is required")
618
- roles = self._validate_roles(roles)
619
- pre_dispatch = self._fire_pre_run(
620
- goal=goal,
621
- roles=roles,
622
- max_retries=max_retries,
623
- user_email=user_email,
624
- scope=scope,
625
- )
626
- orchestrator = self._live_orchestrator(user_email, scope)
627
- mode = getattr(orchestrator, "mode", "llm")
628
- run = self._store.record_agent_run(
629
- agent_id=ROLE_AGENT_IDS.get("executor", "agent:executor"),
630
- status="queued",
631
- input_text=goal,
632
- output_text="",
633
- timeline=[{"event": "agent_started", "status": "queued", "timestamp": _now()}],
634
- relationships=[],
635
- handoffs=[],
636
- context_packets=[],
637
- plan=[],
638
- plan_review={},
639
- review_history=[],
640
- retry_history=[],
641
- memory_snapshots=[],
642
- user_email=user_email or None,
643
- graph=None,
644
- workspace_id=scope,
645
- mode=mode,
646
- )
647
- run = self._store.update_agent_run(
648
- run.get("id"),
649
- workspace_id=scope,
650
- execution_mode="async",
651
- requested_roles=roles or None,
652
- inputs=inputs or {},
653
- # Persist the clamped budget so the durable row reflects what the
654
- # executor will actually honor, not the raw client request.
655
- max_retries=self._clamp_retries(max_retries),
656
- )
657
- payload: Dict[str, Any] = {"run": run}
658
- if pre_dispatch is not None:
659
- payload["pre_run_hooks"] = pre_dispatch
660
- return payload
661
-
662
- def complete_reserved_run(
663
- self,
664
- run_id: str,
665
- goal: str,
666
- *,
667
- user_email: Optional[str],
668
- scope: Optional[str],
669
- roles: Optional[List[str]] = None,
670
- inputs: Optional[Dict[str, Any]] = None,
671
- max_retries: int = 2,
672
- pre_dispatch: Optional[Dict[str, Any]] = None,
673
- cancel_requested: Optional[Callable[[], bool]] = None,
674
- ) -> Dict[str, Any]:
675
- """Execute orchestration and update an existing durable async row."""
676
- run = self._store.get_agent_run(run_id, workspace_id=scope)
677
- base_timeline = list(run.get("timeline") or [])
678
- self._store.update_agent_run(
679
- run_id,
680
- workspace_id=scope,
681
- status="running",
682
- current_role=(roles or list(CORE_PIPELINE))[0] if (roles or CORE_PIPELINE) else None,
683
- started_at=run.get("started_at") or _now(),
684
- )
685
- try:
686
- orchestrator = self._live_orchestrator(user_email, scope)
687
- result = orchestrator.run(
688
- goal,
689
- user_email=user_email or None,
690
- workspace_id=scope,
691
- inputs=inputs or {},
692
- roles=roles or None,
693
- max_retries=self._clamp_retries(max_retries),
694
- )
695
- except Exception as exc:
696
- failed = self._store.update_agent_run(
697
- run_id,
698
- workspace_id=scope,
699
- graph=self._workspace_graph(),
700
- status="failed",
701
- current_role=None,
702
- error=str(exc),
703
- output_text=str(exc),
704
- timeline=base_timeline + [{
705
- "event": "execution_failed",
706
- "status": "failed",
707
- "detail": str(exc),
708
- "timestamp": _now(),
709
- }],
710
- )
711
- self._append_audit_event("multi_agent_run", user_email=user_email, agent_id=failed.get("agent_id"), status="failed", retries=0)
712
- return {"run": failed, "result": {"status": "failed", "error": str(exc)}}
713
-
714
- patch = self._result_patch(result, goal)
715
- patch["timeline"] = base_timeline + list(result.timeline or [])
716
- if cancel_requested is not None and cancel_requested():
717
- patch["status"] = "cancelled"
718
- patch["current_role"] = None
719
- patch["cancel_reason"] = "cancelled after the current synchronous step completed"
720
- patch["cancelled_at"] = _now()
721
- patch["timeline"] = patch["timeline"] + [{
722
- "event": "execution_cancelled",
723
- "status": "cancelled",
724
- "reason": patch["cancel_reason"],
725
- "timestamp": _now(),
726
- }]
727
- updated = self._store.update_agent_run(
728
- run_id,
729
- workspace_id=scope,
730
- graph=self._workspace_graph(),
731
- patch=patch,
732
- )
733
- self._append_audit_event(
734
- "multi_agent_run",
735
- user_email=user_email,
736
- agent_id=result.agent_id,
737
- status=updated.get("status") or result.status,
738
- retries=result.retries,
739
- )
740
- # Large-scale user-visible impact: successful runs enrich the Brain permanently.
741
- if (updated.get("status") or result.status) in ("ok", "retried_ok"):
742
- self._synthesize_brain_memory(goal=goal, result=result, user_email=user_email, scope=scope)
743
- post_dispatch = self._post_run_hooks(
744
- run_id=run_id,
745
- result=result,
746
- user_email=user_email,
747
- scope=scope,
748
- status=updated.get("status") or result.status,
749
- )
750
- result_payload = result.as_dict()
751
- result_payload["contract"] = multi_agent_contract(
752
- result=result,
753
- goal=goal,
754
- run_id=run_id,
755
- current_role=updated.get("current_role") if isinstance(updated, dict) else None,
756
- )
757
- if updated.get("status") == "cancelled":
758
- result_payload = {"status": "cancelled", "reason": updated.get("cancel_reason"), "completed_result": result_payload}
759
- payload = {"run": updated, "result": result_payload}
760
- if pre_dispatch is not None:
761
- payload["pre_run_hooks"] = pre_dispatch
762
- if post_dispatch is not None:
763
- payload["post_run_hooks"] = post_dispatch
764
- return payload
765
-
766
- def start(
767
- self,
768
- goal: str,
769
- *,
770
- user_email: Optional[str],
771
- scope: Optional[str],
772
- roles: Optional[List[str]] = None,
773
- inputs: Optional[Dict[str, Any]] = None,
774
- max_retries: int = 2,
775
- ) -> Dict[str, Any]:
776
- if not str(goal or "").strip():
777
- raise ValueError("goal is required")
778
- roles = self._validate_roles(roles)
779
-
780
- pre_dispatch = self._fire_pre_run(
781
- goal=goal,
782
- roles=roles,
783
- max_retries=max_retries,
784
- user_email=user_email,
785
- scope=scope,
786
- )
787
- orchestrator = self._live_orchestrator(user_email, scope)
788
- result = orchestrator.run(
789
- goal,
790
- user_email=user_email or None,
791
- workspace_id=scope,
792
- inputs=inputs or {},
793
- roles=roles or None,
794
- max_retries=self._clamp_retries(max_retries),
795
- )
796
- run = self._store.record_agent_run(
797
- agent_id=result.agent_id,
798
- status=result.status,
799
- input_text=goal,
800
- output_text=result.output,
801
- timeline=result.timeline,
802
- relationships=[ROLE_AGENT_IDS.get(r, f"agent:{r}") for r in result.roles_run],
803
- handoffs=result.handoffs,
804
- context_packets=result.context_packets,
805
- plan=result.plan,
806
- plan_review=result.plan_review,
807
- review_history=result.review_history,
808
- retry_history=result.retry_history,
809
- memory_snapshots=result.memory_snapshots,
810
- user_email=user_email or None,
811
- graph=self._workspace_graph(),
812
- workspace_id=scope,
813
- mode=getattr(result, "mode", "simulation"),
814
- )
815
- self._append_audit_event(
816
- "multi_agent_run",
817
- user_email=user_email,
818
- agent_id=result.agent_id,
819
- status=result.status,
820
- retries=result.retries,
821
- )
822
- # Large-scale user-visible impact: successful runs enrich the Brain permanently.
823
- if result.status in ("ok", "retried_ok"):
824
- self._synthesize_brain_memory(goal=goal, result=result, user_email=user_email, scope=scope)
825
-
826
- run_id = run.get("id") or run.get("run_id") if isinstance(run, dict) else None
827
- post_dispatch = self._post_run_hooks(
828
- run_id=run_id,
829
- result=result,
830
- user_email=user_email,
831
- scope=scope,
832
- )
833
-
834
- result_payload = result.as_dict()
835
- result_payload["contract"] = multi_agent_contract(result=result, goal=goal, run_id=run.get("id") if isinstance(run, dict) else None)
836
- payload = {"run": run, "result": result_payload}
837
- if pre_dispatch is not None:
838
- payload["pre_run_hooks"] = pre_dispatch
839
- if post_dispatch is not None:
840
- payload["post_run_hooks"] = post_dispatch
841
- return payload
842
-
843
- def stop(self, run_id: str, *, scope: Optional[str] = None) -> Dict[str, Any]:
844
- """Best-effort stop.
845
-
846
- The default runtime executes synchronously, so by the time a run id
847
- exists the run has already completed. Report that honestly rather than
848
- pretending a cancellation occurred.
849
- """
850
- if self._run_executor is not None:
851
- return self._run_executor.cancel(run_id, kind="agent", scope=scope)
852
- try:
853
- run = self._store.get_agent_run(run_id, workspace_id=scope)
854
- except FileNotFoundError:
855
- return {"stopped": False, "reason": "run not found", "run_id": run_id}
856
- status = str(run.get("status") or "")
857
- if status in _ACTIVE_STATUSES:
858
- return {"stopped": False, "reason": "asynchronous cancellation is not supported by the synchronous runtime", "run_id": run_id, "status": status}
859
- return {"stopped": False, "reason": "run already finished", "run_id": run_id, "status": status}