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,493 +0,0 @@
1
- """Pure helpers for the single-agent runtime.
2
-
3
- Extracted from :mod:`latticeai.core.agent` so the state machine module
4
- owns only the loop. Every function here is deterministic and free of
5
- I/O / side effects (aside from reading environment in the budget
6
- ``from_env`` constructors).
7
-
8
- Public names are re-exported from :mod:`latticeai.core.agent` so callers
9
- keep importing from the original location.
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import ast
15
- import json
16
- import re
17
- from dataclasses import dataclass
18
- from pathlib import Path
19
- from typing import Any, Dict, FrozenSet, List, Mapping, Optional, Tuple
20
-
21
- from latticeai.core.agent_state import AgentState
22
- from latticeai.core.file_generation import infer_file_target, infer_project_manifest
23
-
24
- # ── action parsing ─────────────────────────────────────────────────────
25
-
26
- _THINK_BLOCK_RE = re.compile(
27
- r"<(think|thinking|reasoning)>.*?</\1>", flags=re.DOTALL | re.IGNORECASE
28
- )
29
-
30
-
31
- def extract_action_details(raw: str) -> Tuple[Dict, List[str]]:
32
- """Parse one JSON action object out of an LLM response (tolerant of fences/prose).
33
-
34
- Returns ``(action, repairs)`` where ``repairs`` names every tolerance that
35
- was needed — the loop trace and the weak-model robustness harness consume
36
- it to measure how much help a given model needs.
37
- """
38
- repairs: List[str] = []
39
- # Small local models often prepend <think>...</think> reasoning that can
40
- # itself contain braces — drop it before locating the action object.
41
- text = _THINK_BLOCK_RE.sub("", raw).strip()
42
- if text != str(raw).strip():
43
- repairs.append("think_strip")
44
- fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, flags=re.DOTALL)
45
- if fenced:
46
- text = fenced.group(1).strip()
47
- repairs.append("fence")
48
- elif not text.startswith("{"):
49
- start = text.find("{")
50
- end = text.rfind("}")
51
- if start >= 0 and end > start:
52
- text = text[start : end + 1]
53
- repairs.append("slice")
54
-
55
- action: Any = None
56
- try:
57
- action = json.loads(text)
58
- except json.JSONDecodeError:
59
- # Second chance for the most common small-model JSON slips: trailing
60
- # commas before a closing brace/bracket.
61
- repaired = re.sub(r",\s*([}\]])", r"\1", text)
62
- try:
63
- action = json.loads(repaired)
64
- repairs.append("trailing_comma")
65
- except json.JSONDecodeError as exc:
66
- # Last chance: weak models sometimes emit a Python dict literal
67
- # (single quotes, True/False/None). ast.literal_eval parses that
68
- # deterministically without evaluating code.
69
- try:
70
- literal = ast.literal_eval(text)
71
- except (ValueError, SyntaxError):
72
- raise ValueError(f"Agent did not return valid JSON: {exc}") from exc
73
- if not isinstance(literal, dict):
74
- raise ValueError(f"Agent did not return valid JSON: {exc}") from exc
75
- action = literal
76
- repairs.append("python_literal")
77
-
78
- if not isinstance(action, dict) or "action" not in action:
79
- raise ValueError("Agent JSON must include an action field.")
80
- return action, repairs
81
-
82
-
83
- def extract_action(raw: str) -> Dict:
84
- """Back-compat wrapper over :func:`extract_action_details`."""
85
- action, _ = extract_action_details(raw)
86
- return action
87
-
88
-
89
- # ── plan normalization ─────────────────────────────────────────────────
90
-
91
- _FILE_CREATE_PLAN_ACTIONS = frozenset({"write_file", "generate_file"})
92
-
93
-
94
- def _plan_misses_manifest(steps: List[Dict[str, Any]], manifest: Dict[str, Any]) -> bool:
95
- """True when a pure file-writing plan fails to cover the manifest's file types.
96
-
97
- Only pure file-creation plans are candidates for rewriting — a plan with
98
- read/search steps reflects real planner intent and stays untouched.
99
- """
100
- if any(s.get("action") not in _FILE_CREATE_PLAN_ACTIONS for s in steps):
101
- return False
102
-
103
- def _ext(path: Any) -> str:
104
- text = str(path or "")
105
- dot = text.rfind(".")
106
- return text[dot:].lower() if dot >= 0 else ""
107
-
108
- planned_exts = {
109
- _ext((s.get("args") or {}).get("path")) for s in steps
110
- }
111
- manifest_exts = {_ext(spec.get("path")) for spec in manifest.get("files", [])}
112
- return not manifest_exts.issubset(planned_exts)
113
-
114
-
115
- def normalize_plan(plan: Any, user_message: str) -> Tuple[Dict[str, Any], List[str]]:
116
- """Enforce the minimal plan schema so execution never starts adrift.
117
-
118
- A weak planner that returns junk steps / a missing goal previously flowed
119
- straight into the executor, which then had to reconstruct intent from the
120
- raw request. Normalization keeps the loop honest: ``goal`` is always a
121
- non-empty string, ``steps`` only contains dicts with an ``action``, and an
122
- empty plan for an obvious file-creation request gets a deterministic
123
- single ``write_file`` step instead of leaving the executor to improvise.
124
-
125
- Returns ``(plan, fixes)`` where ``fixes`` names every applied repair —
126
- the loop trace records them so plan quality is observable per model.
127
- """
128
- fixes: List[str] = []
129
- if not isinstance(plan, dict):
130
- plan = {}
131
- fixes.append("plan_not_object")
132
- plan = dict(plan)
133
-
134
- goal = str(plan.get("goal") or "").strip()
135
- if not goal:
136
- plan["goal"] = user_message
137
- fixes.append("goal_defaulted")
138
-
139
- raw_steps = plan.get("steps")
140
- steps = [
141
- s for s in (raw_steps if isinstance(raw_steps, list) else [])
142
- if isinstance(s, dict) and s.get("action")
143
- ]
144
- if raw_steps and steps != raw_steps:
145
- fixes.append("steps_filtered")
146
-
147
- # Manifest-aware planning (review Wave 0.4): when the request is a
148
- # recognized multi-file project, the deterministic manifest — not the
149
- # planner's improvisation — decides the file set, exactly like the direct
150
- # chat path. Rewrites apply only when the plan is empty or is a pure
151
- # file-writing plan that misses part of the manifest, so a planner that
152
- # already covered every requested file type is left untouched.
153
- manifest = infer_project_manifest(user_message)
154
- if manifest:
155
- manifest_steps = [{
156
- "action": "write_file",
157
- "args": {"path": spec["path"]},
158
- "description": spec["brief"],
159
- } for spec in manifest["files"]]
160
- if not steps:
161
- steps = manifest_steps
162
- fixes.append("manifest_steps")
163
- elif _plan_misses_manifest(steps, manifest):
164
- steps = manifest_steps
165
- fixes.append("manifest_rewrite")
166
-
167
- if not steps:
168
- inferred = infer_file_target(user_message)
169
- if inferred:
170
- steps = [{
171
- "action": "write_file",
172
- "args": {"path": inferred},
173
- "description": f"Create {inferred} for: {user_message[:120]}",
174
- }]
175
- fixes.append("heuristic_file_step")
176
- plan["steps"] = steps
177
-
178
- try:
179
- estimated = int(plan.get("estimated_steps") or 0)
180
- except (TypeError, ValueError):
181
- estimated = 0
182
- fixes.append("estimated_steps_invalid")
183
- plan["estimated_steps"] = max(1, estimated, len(steps))
184
- plan["requires_approval"] = bool(plan.get("requires_approval", False))
185
- if not isinstance(plan.get("rollback_strategy"), str):
186
- plan["rollback_strategy"] = "none"
187
- return plan, fixes
188
-
189
-
190
- # ── learnings filter ───────────────────────────────────────────────────
191
-
192
- _TRIVIAL_LEARNING_RE = re.compile(
193
- r"^(파일(을|이)?\s*(만들|생성|작성|저장)|작업(을|이)?\s*(완료|성공)|성공적으로"
194
- r"|task\s+(was\s+)?complet|file\s+(was\s+)?(creat|written|saved)"
195
- r"|(successfully\s+)?(created|completed|finished|done)\b)",
196
- re.IGNORECASE,
197
- )
198
-
199
-
200
- def filter_learnings(learnings: List[Any]) -> List[str]:
201
- """Drop trivial/duplicate learnings before they enter the brain.
202
-
203
- "파일을 만들었다"-class statements restate what the transcript already
204
- records and pollute recall. A learning survives when it is long enough to
205
- carry information and is not a bare completion announcement.
206
- """
207
- kept: List[str] = []
208
- seen: set = set()
209
- for raw in learnings or []:
210
- text = str(raw or "").strip()
211
- if len(text) < 12:
212
- continue
213
- if _TRIVIAL_LEARNING_RE.match(text) and len(text) < 48:
214
- continue
215
- key = text.lower()
216
- if key in seen:
217
- continue
218
- seen.add(key)
219
- kept.append(text)
220
- return kept
221
-
222
-
223
- # ── transcript shaping ─────────────────────────────────────────────────
224
-
225
- def _truncate_strings(value: Any, limit: int) -> Any:
226
- """Deep-copy ``value`` with every string capped at ``limit`` chars.
227
-
228
- Long tool outputs (file bodies, command output) dominate executor prompt
229
- size without adding decision-relevant signal. The cap keeps the head of
230
- each string and names how much was dropped, so the model still sees what
231
- the value was — never a silent hole.
232
- """
233
- if isinstance(value, str):
234
- if len(value) <= limit:
235
- return value
236
- return value[:limit] + f"…[+{len(value) - limit} chars]"
237
- if isinstance(value, dict):
238
- return {k: _truncate_strings(v, limit) for k, v in value.items()}
239
- if isinstance(value, list):
240
- return [_truncate_strings(v, limit) for v in value]
241
- return value
242
-
243
-
244
- def compact_transcript(
245
- transcript: List[Dict[str, Any]],
246
- *,
247
- window: int = 8,
248
- result_chars: int = 700,
249
- ) -> List[Dict[str, Any]]:
250
- """Bounded executor view of a transcript (review Wave 0.3).
251
-
252
- The executor prompt previously embedded the *entire* transcript JSON every
253
- step — O(steps²) token growth that starved :class:`PhaseBudgets` on long
254
- runs and buried weak models in stale detail. This view keeps the most
255
- recent ``window`` steps in full (with string values capped at
256
- ``result_chars``) and reduces every older step to a one-line summary, so
257
- the prompt stays bounded while no step disappears entirely.
258
- """
259
- steps = list(transcript or [])
260
- if len(steps) <= window:
261
- return [_truncate_strings(step, result_chars) for step in steps]
262
- older, recent = steps[:-window], steps[-window:]
263
- summarized: List[Dict[str, Any]] = [{
264
- "summarized_older_steps": len(older),
265
- "note": "older steps compacted — full detail retained in the run record",
266
- }]
267
- for step in older:
268
- entry: Dict[str, Any] = {"state": step.get("state")}
269
- for key in ("action", "verdict", "retry_attempt"):
270
- if step.get(key) is not None:
271
- entry[key] = step.get(key)
272
- if step.get("error"):
273
- entry["error"] = str(step["error"])[:160]
274
- elif isinstance(step.get("result"), dict):
275
- entry["ok"] = True
276
- path = step["result"].get("path") or (step.get("args") or {}).get("path")
277
- if path:
278
- entry["path"] = str(path)
279
- summarized.append(entry)
280
- summarized.extend(_truncate_strings(step, result_chars) for step in recent)
281
- return summarized
282
-
283
-
284
- def files_written(
285
- transcript: List[Dict[str, Any]],
286
- file_create_actions: FrozenSet[str],
287
- ) -> List[str]:
288
- """Ordered unique paths of files this run successfully wrote (review L5).
289
-
290
- Later executor steps get this as explicit context, so "만들고 이어서
291
- 설명해" multi-step work sees its own output instead of a stale
292
- workspace picture.
293
- """
294
- seen: List[str] = []
295
- for step in transcript:
296
- if step.get("state") != AgentState.EXECUTING.value:
297
- continue
298
- if step.get("action") not in file_create_actions:
299
- continue
300
- if not isinstance(step.get("result"), dict):
301
- continue
302
- path = step["result"].get("path") or (step.get("args") or {}).get("path")
303
- if path and str(path) not in seen:
304
- seen.append(str(path))
305
- return seen
306
-
307
-
308
- def artifact_checklist(
309
- transcript: List[Dict[str, Any]],
310
- file_create_actions: FrozenSet[str],
311
- ) -> List[Dict[str, Any]]:
312
- """Deterministic artifact facts for the critic (review L4).
313
-
314
- The critic previously judged file work from prose alone; this surfaces
315
- the sanitize/repair honesty flags per written file so a repaired
316
- placeholder can never pass as a fulfilled request unchecked.
317
- """
318
- checklist: List[Dict[str, Any]] = []
319
- for step in transcript:
320
- if step.get("state") != AgentState.EXECUTING.value:
321
- continue
322
- if step.get("action") not in file_create_actions:
323
- continue
324
- if not isinstance(step.get("result"), dict):
325
- continue
326
- path = step["result"].get("path") or (step.get("args") or {}).get("path")
327
- if not path:
328
- continue
329
- sanitize_meta = step.get("content_sanitize") or {}
330
- checklist.append({
331
- "path": str(path),
332
- "sanitized": bool(sanitize_meta.get("sanitized")),
333
- "repaired": bool(sanitize_meta.get("repaired")),
334
- })
335
- return checklist
336
-
337
-
338
- # Explicit requirement lines a user writes out: "- 다크모드", "1. 검색 기능",
339
- # "* dark mode". Free prose is deliberately NOT parsed — a wrong requirement
340
- # is worse than no requirement.
341
- _REQUIREMENT_LINE_RE = re.compile(r"^\s*(?:[-*•]|\d+[.)])\s+(.{3,120})$", re.MULTILINE)
342
-
343
-
344
- def requirement_coverage(
345
- user_message: str,
346
- transcript: List[Dict[str, Any]],
347
- file_create_actions: FrozenSet[str],
348
- ) -> Dict[str, Any]:
349
- """Did the run produce what the request actually asked for? (review 루프 §2)
350
-
351
- The critic judged prose against prose, so "make an HTML+CSS+JS todo app"
352
- could pass with one file written. This is the deterministic half of the
353
- answer, built from two sources that are honest about their own limits:
354
-
355
- * **manifest files** — when the request maps to a known multi-file project
356
- (:func:`infer_project_manifest`), every declared path must have been
357
- written. A missing manifest file is a *hard* miss: it is not a matter of
358
- taste whether ``style.css`` exists.
359
- * **explicit requirement lines** — bullet/numbered lines the user wrote
360
- out. These are reported to the critic as a checklist but never block on
361
- their own: matching a feature to a transcript is a judgement call, and
362
- guessing it wrong would either fake completion or block real work.
363
-
364
- Returns ``{"files": {...}, "requirements": [...], "missing_files": [...],
365
- "complete": bool}`` where ``complete`` is false only when a declared
366
- manifest file is missing.
367
- """
368
- written = files_written(transcript, file_create_actions)
369
- written_names = {Path(path).name.lower() for path in written}
370
- manifest = infer_project_manifest(user_message) or {}
371
- declared = [str(spec.get("path") or "") for spec in manifest.get("files", [])]
372
- missing = [
373
- path for path in declared
374
- if path and Path(path).name.lower() not in written_names
375
- ]
376
- requirements = [
377
- line.strip()
378
- for line in _REQUIREMENT_LINE_RE.findall(str(user_message or ""))
379
- ][:10]
380
- return {
381
- "files": {"declared": declared, "written": written},
382
- "missing_files": missing,
383
- "requirements": requirements,
384
- "complete": not missing,
385
- }
386
-
387
-
388
- def format_requirement_coverage(coverage: Dict[str, Any]) -> str:
389
- """Requirement facts for the critic prompt, or "" when there is nothing."""
390
- lines: List[str] = []
391
- declared = coverage["files"]["declared"]
392
- if declared:
393
- written = set(coverage["files"]["written"])
394
- lines.append("Requested files (deterministic, from the request):")
395
- for path in declared:
396
- got = any(Path(item).name.lower() == Path(path).name.lower() for item in written)
397
- lines.append(f"- {path}: {'written' if got else 'MISSING'}")
398
- if coverage["requirements"]:
399
- lines.append(
400
- "Requirements the user listed explicitly — check each one against "
401
- "the artifacts, not against the plan:"
402
- )
403
- lines.extend(f"- {item}" for item in coverage["requirements"])
404
- return "\n\n" + "\n".join(lines) if lines else ""
405
-
406
-
407
- def format_artifact_checklist(checklist: List[Dict[str, Any]]) -> str:
408
- lines = []
409
- for item in checklist:
410
- state = (
411
- "auto-REPAIRED scaffold" if item["repaired"]
412
- else ("sanitized model output" if item["sanitized"] else "written as produced")
413
- )
414
- lines.append(f"- {item['path']}: {state}")
415
- return (
416
- "Artifact checklist (deterministic, from the transcript):\n"
417
- + "\n".join(lines)
418
- + "\nVerify each artifact actually fulfills the user's request. An "
419
- "auto-repaired scaffold is NOT completion unless its content "
420
- "satisfies what was asked."
421
- )
422
-
423
-
424
- # ── budgets ────────────────────────────────────────────────────────────
425
-
426
- @dataclass(frozen=True)
427
- class TranscriptBudget:
428
- """Executor/critic prompt shaping caps (review Wave 0.3).
429
-
430
- ``window`` full recent steps for the executor; per-string caps keep tool
431
- output bodies from dominating either prompt. Overridable through the same
432
- ``Config.from_env`` pattern as :class:`PhaseBudgets`.
433
- """
434
-
435
- window: int = 8
436
- result_chars: int = 700
437
- verify_chars: int = 1200
438
-
439
- @classmethod
440
- def from_env(cls, env: Optional[Mapping[str, str]] = None) -> "TranscriptBudget":
441
- from latticeai.core.config import _int
442
-
443
- if env is None:
444
- import os
445
-
446
- env = os.environ
447
-
448
- def cap(key: str, default: int, floor: int) -> int:
449
- return max(floor, _int(env, key, default))
450
-
451
- return cls(
452
- window=cap("LATTICEAI_AGENT_TRANSCRIPT_WINDOW", cls.window, 2),
453
- result_chars=cap("LATTICEAI_AGENT_TRANSCRIPT_CHARS", cls.result_chars, 120),
454
- verify_chars=cap("LATTICEAI_AGENT_VERIFY_CHARS", cls.verify_chars, 200),
455
- )
456
-
457
-
458
- @dataclass(frozen=True)
459
- class PhaseBudgets:
460
- """Per-phase token budgets for the agent loop.
461
-
462
- One shared budget let a weak model burn everything on planning prose and
463
- reach EXECUTE with nothing left. Each role phase now has its own cap, so
464
- a verbose planner can never starve execution or verification. Defaults
465
- match the historical hardcoded values; every cap is overridable through
466
- the ``Config.from_env`` environment pattern.
467
- """
468
-
469
- plan_tokens: int = 1024
470
- execute_tokens: int = 4096
471
- verify_tokens: int = 512
472
- memory_tokens: int = 256
473
-
474
- @classmethod
475
- def from_env(cls, env: Optional[Mapping[str, str]] = None) -> "PhaseBudgets":
476
- from latticeai.core.config import _int
477
-
478
- if env is None:
479
- import os
480
-
481
- env = os.environ
482
-
483
- def cap(key: str, default: int) -> int:
484
- # A misconfigured/absurd value must not brick the loop: floor at a
485
- # budget that still fits one JSON action object.
486
- return max(128, _int(env, key, default))
487
-
488
- return cls(
489
- plan_tokens=cap("LATTICEAI_AGENT_PLAN_TOKENS", cls.plan_tokens),
490
- execute_tokens=cap("LATTICEAI_AGENT_EXECUTE_TOKENS", cls.execute_tokens),
491
- verify_tokens=cap("LATTICEAI_AGENT_VERIFY_TOKENS", cls.verify_tokens),
492
- memory_tokens=cap("LATTICEAI_AGENT_MEMORY_TOKENS", cls.memory_tokens),
493
- )
@@ -1,110 +0,0 @@
1
- """Agent profiles — matching the loop to the model driving it (v9.9.7).
2
-
3
- The single-agent loop asks a model to emit one strict JSON tool call per turn.
4
- Large models do that reliably; a 1–4B local model routinely does not, and the
5
- loop's answer was always the same: repair, correct, retry, and eventually give
6
- up with nothing written. The counters showed the strain (``model_strain`` in
7
- the run explanation), but every model still ran the same loop.
8
-
9
- A profile is that missing dial:
10
-
11
- * **standard** — today's behaviour, unchanged, for models that can hold a
12
- tool-call contract.
13
- * **compact** — for small local models: a shorter transcript window, an
14
- earlier escalation to naming the valid tools, and — the important one — a
15
- **direct-path fallback**. When JSON tool calls keep failing, the loop stops
16
- asking for JSON at all and executes the plan's own file steps, requesting
17
- only *file content* in plain text. Weak models are bad at tool protocols and
18
- fine at writing a file; the profile plays to that.
19
-
20
- Selection is deterministic and inspectable: a model id names its size, so the
21
- profile is a pure function of the id plus an explicit environment override.
22
- Nothing here guesses at quality — a model absent from the size heuristic gets
23
- ``standard``, which is the conservative choice (no behaviour change).
24
- """
25
-
26
- from __future__ import annotations
27
-
28
- import re
29
- from dataclasses import dataclass
30
- from typing import Mapping, Optional
31
-
32
- __all__ = ["AgentProfile", "COMPACT", "STANDARD", "profile_for_model", "model_size_b"]
33
-
34
-
35
- @dataclass(frozen=True)
36
- class AgentProfile:
37
- """How hard the loop should work to keep a given model on contract."""
38
-
39
- name: str
40
- # Executor transcript window (steps kept in full). Small models drown in
41
- # long transcripts far sooner than large ones.
42
- transcript_window: int
43
- # Format slips tolerated before the run stops retrying.
44
- parse_failure_budget: int
45
- # Slip count at which the correction hint starts naming valid tool names.
46
- escalate_after: int
47
- # When JSON tool calls are exhausted, execute the plan's file steps
48
- # directly and ask only for file content in plain text.
49
- direct_path_fallback: bool
50
-
51
-
52
- STANDARD = AgentProfile(
53
- name="standard",
54
- transcript_window=8,
55
- parse_failure_budget=3,
56
- escalate_after=2,
57
- direct_path_fallback=False,
58
- )
59
-
60
- COMPACT = AgentProfile(
61
- name="compact",
62
- transcript_window=4,
63
- parse_failure_budget=4,
64
- escalate_after=1,
65
- direct_path_fallback=True,
66
- )
67
-
68
- _PROFILES = {profile.name: profile for profile in (STANDARD, COMPACT)}
69
-
70
- # "gemma-3-4b-it-4bit", "qwen2.5-1.5b", "llama-3.2-3B" → parameter count in B.
71
- # The quantization suffix ("4bit", "8bit") is deliberately excluded: it is not
72
- # a parameter count, and reading it as one would mislabel every quantized model.
73
- _SIZE_RE = re.compile(r"(?<![a-z0-9.])(\d+(?:\.\d+)?)\s*b(?![a-z0-9])", re.IGNORECASE)
74
- _QUANT_RE = re.compile(r"\d+\s*bit", re.IGNORECASE)
75
-
76
- # At or below this parameter count, the compact profile applies.
77
- COMPACT_MAX_PARAMS_B = 4.0
78
-
79
-
80
- def model_size_b(model_id: str) -> Optional[float]:
81
- """Parameter count in billions parsed from a model id, or None.
82
-
83
- Returns None rather than guessing when the id names no size — an unknown
84
- model must not be silently downgraded to the compact loop.
85
- """
86
- text = _QUANT_RE.sub(" ", str(model_id or ""))
87
- sizes = [float(match) for match in _SIZE_RE.findall(text)]
88
- return min(sizes) if sizes else None
89
-
90
-
91
- def profile_for_model(
92
- model_id: Optional[str], *, env: Optional[Mapping[str, str]] = None
93
- ) -> AgentProfile:
94
- """Pick the loop profile for a model.
95
-
96
- Order: explicit ``LATTICEAI_AGENT_PROFILE`` override → size heuristic →
97
- ``standard``. An unrecognized override name falls through to the
98
- heuristic rather than failing the run.
99
- """
100
- if env is None:
101
- import os
102
-
103
- env = os.environ
104
- override = str(env.get("LATTICEAI_AGENT_PROFILE") or "").strip().lower()
105
- if override in _PROFILES:
106
- return _PROFILES[override]
107
- size = model_size_b(model_id or "")
108
- if size is not None and size <= COMPACT_MAX_PARAMS_B:
109
- return COMPACT
110
- return STANDARD