langchain-agentx-python 0.1__py3-none-any.whl

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 (354) hide show
  1. langchain_agentx/__init__.py +46 -0
  2. langchain_agentx/command/__init__.py +28 -0
  3. langchain_agentx/command/builtin/__init__.py +25 -0
  4. langchain_agentx/command/builtin/clear.py +33 -0
  5. langchain_agentx/command/builtin/compact.py +33 -0
  6. langchain_agentx/command/builtin/memory.py +37 -0
  7. langchain_agentx/command/builtin/reload_plugins.py +42 -0
  8. langchain_agentx/command/context.py +30 -0
  9. langchain_agentx/command/dispatcher.py +183 -0
  10. langchain_agentx/command/registry.py +110 -0
  11. langchain_agentx/command/result.py +25 -0
  12. langchain_agentx/command/types.py +41 -0
  13. langchain_agentx/config/__init__.py +14 -0
  14. langchain_agentx/loop/__init__.py +47 -0
  15. langchain_agentx/loop/config/__init__.py +20 -0
  16. langchain_agentx/loop/config/agent_config.py +66 -0
  17. langchain_agentx/loop/config/agent_loop_config.py +72 -0
  18. langchain_agentx/loop/config/model_context_resolver.py +105 -0
  19. langchain_agentx/loop/config/runtime_settings.py +50 -0
  20. langchain_agentx/loop/config/token_estimator.py +133 -0
  21. langchain_agentx/loop/context/__init__.py +66 -0
  22. langchain_agentx/loop/context/blocking_guard.py +97 -0
  23. langchain_agentx/loop/context/compaction_service.py +60 -0
  24. langchain_agentx/loop/context/message_utils.py +56 -0
  25. langchain_agentx/loop/context/pipeline.py +127 -0
  26. langchain_agentx/loop/context/settings.py +103 -0
  27. langchain_agentx/loop/context/stages/__init__.py +29 -0
  28. langchain_agentx/loop/context/stages/autocompact.py +140 -0
  29. langchain_agentx/loop/context/stages/base.py +32 -0
  30. langchain_agentx/loop/context/stages/collapse.py +76 -0
  31. langchain_agentx/loop/context/stages/microcompact.py +76 -0
  32. langchain_agentx/loop/context/stages/noop.py +33 -0
  33. langchain_agentx/loop/context/stages/snip.py +71 -0
  34. langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
  35. langchain_agentx/loop/context/types.py +79 -0
  36. langchain_agentx/loop/exit/__init__.py +1 -0
  37. langchain_agentx/loop/exit/exit_logic.py +320 -0
  38. langchain_agentx/loop/exit/reason_codes.py +39 -0
  39. langchain_agentx/loop/graph/__init__.py +5 -0
  40. langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
  41. langchain_agentx/loop/graph/factory.py +1409 -0
  42. langchain_agentx/loop/graph/graph_edges.py +820 -0
  43. langchain_agentx/loop/hook/__init__.py +48 -0
  44. langchain_agentx/loop/hook/async_hook_runner.py +62 -0
  45. langchain_agentx/loop/hook/config.py +280 -0
  46. langchain_agentx/loop/hook/engine.py +321 -0
  47. langchain_agentx/loop/hook/executors/__init__.py +9 -0
  48. langchain_agentx/loop/hook/executors/agent.py +107 -0
  49. langchain_agentx/loop/hook/executors/command.py +230 -0
  50. langchain_agentx/loop/hook/executors/http.py +114 -0
  51. langchain_agentx/loop/hook/executors/prompt.py +92 -0
  52. langchain_agentx/loop/hook/graph_wiring.py +134 -0
  53. langchain_agentx/loop/hook/registry.py +262 -0
  54. langchain_agentx/loop/hook/trust.py +43 -0
  55. langchain_agentx/loop/hook/types.py +110 -0
  56. langchain_agentx/loop/injection/__init__.py +13 -0
  57. langchain_agentx/loop/injection/dedup.py +74 -0
  58. langchain_agentx/loop/loop_abort.py +36 -0
  59. langchain_agentx/loop/model/__init__.py +1 -0
  60. langchain_agentx/loop/model/model_node.py +648 -0
  61. langchain_agentx/loop/model/model_nodes.py +661 -0
  62. langchain_agentx/loop/model/orphan_tool_results.py +38 -0
  63. langchain_agentx/loop/model/retrier.py +307 -0
  64. langchain_agentx/loop/model/retry_bridge.py +58 -0
  65. langchain_agentx/loop/model/retry_events.py +35 -0
  66. langchain_agentx/loop/model/retry_policy.py +56 -0
  67. langchain_agentx/loop/model/schema_and_format.py +153 -0
  68. langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
  69. langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
  70. langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
  71. langchain_agentx/loop/prompt/__init__.py +95 -0
  72. langchain_agentx/loop/prompt/builder.py +61 -0
  73. langchain_agentx/loop/prompt/builtin.py +218 -0
  74. langchain_agentx/loop/prompt/compact.py +408 -0
  75. langchain_agentx/loop/prompt/sections.py +120 -0
  76. langchain_agentx/loop/runtime/__init__.py +19 -0
  77. langchain_agentx/loop/runtime/context.py +34 -0
  78. langchain_agentx/loop/runtime/context_factory.py +107 -0
  79. langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
  80. langchain_agentx/loop/subagent/__init__.py +53 -0
  81. langchain_agentx/loop/subagent/async_runner.py +215 -0
  82. langchain_agentx/loop/subagent/context.py +209 -0
  83. langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
  84. langchain_agentx/loop/subagent/graph.py +72 -0
  85. langchain_agentx/loop/subagent/orchestrator.py +391 -0
  86. langchain_agentx/loop/subagent/progress.py +30 -0
  87. langchain_agentx/loop/subagent/prompt.py +52 -0
  88. langchain_agentx/loop/subagent/runner.py +504 -0
  89. langchain_agentx/loop/subagent/transcript.py +172 -0
  90. langchain_agentx/memory/__init__.py +2 -0
  91. langchain_agentx/memory/instruction/__init__.py +12 -0
  92. langchain_agentx/memory/instruction/loader.py +325 -0
  93. langchain_agentx/memory/instruction/resolver.py +24 -0
  94. langchain_agentx/memory/instruction/runtime.py +83 -0
  95. langchain_agentx/memory/instruction/sections.py +83 -0
  96. langchain_agentx/memory/instruction/types.py +59 -0
  97. langchain_agentx/memory/memdir/__init__.py +77 -0
  98. langchain_agentx/memory/memdir/age.py +36 -0
  99. langchain_agentx/memory/memdir/agent_memory.py +380 -0
  100. langchain_agentx/memory/memdir/extractor.py +309 -0
  101. langchain_agentx/memory/memdir/loader.py +187 -0
  102. langchain_agentx/memory/memdir/paths.py +63 -0
  103. langchain_agentx/memory/memdir/recall.py +45 -0
  104. langchain_agentx/memory/memdir/runtime.py +43 -0
  105. langchain_agentx/memory/memdir/scan.py +135 -0
  106. langchain_agentx/memory/memdir/types.py +104 -0
  107. langchain_agentx/memory/session/__init__.py +76 -0
  108. langchain_agentx/memory/session/compact_bridge.py +208 -0
  109. langchain_agentx/memory/session/prompts.py +172 -0
  110. langchain_agentx/memory/session/session_memory.py +282 -0
  111. langchain_agentx/observability/__init__.py +67 -0
  112. langchain_agentx/observability/evaluation/__init__.py +17 -0
  113. langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
  114. langchain_agentx/observability/evaluation/checkers/base.py +34 -0
  115. langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
  116. langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
  117. langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
  118. langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
  119. langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
  120. langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
  121. langchain_agentx/observability/evaluation/service.py +102 -0
  122. langchain_agentx/observability/evaluation/state.py +32 -0
  123. langchain_agentx/observability/evaluation/store.py +258 -0
  124. langchain_agentx/observability/events/__init__.py +15 -0
  125. langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
  126. langchain_agentx/observability/logging/__init__.py +15 -0
  127. langchain_agentx/observability/logging/debug_burst.py +95 -0
  128. langchain_agentx/observability/logging/logging_config.py +178 -0
  129. langchain_agentx/observability/logging/logging_contract.py +65 -0
  130. langchain_agentx/observability/replay/__init__.py +35 -0
  131. langchain_agentx/observability/replay/cli.py +91 -0
  132. langchain_agentx/observability/replay/service.py +83 -0
  133. langchain_agentx/observability/replay/store.py +278 -0
  134. langchain_agentx/observability/replay/ui.py +47 -0
  135. langchain_agentx/observability/trace/__init__.py +25 -0
  136. langchain_agentx/observability/trace/collector.py +560 -0
  137. langchain_agentx/observability/trace/event_emitter.py +183 -0
  138. langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
  139. langchain_agentx/observability/trace/models.py +144 -0
  140. langchain_agentx/observability/trace/sqlite_store.py +873 -0
  141. langchain_agentx/observability/trace/trace_callback.py +295 -0
  142. langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
  143. langchain_agentx/plugin/__init__.py +26 -0
  144. langchain_agentx/plugin/builtin.py +53 -0
  145. langchain_agentx/plugin/config.py +113 -0
  146. langchain_agentx/plugin/loader.py +386 -0
  147. langchain_agentx/plugin/manifest.py +154 -0
  148. langchain_agentx/plugin/registries.py +211 -0
  149. langchain_agentx/plugin/types.py +142 -0
  150. langchain_agentx/provider/__init__.py +27 -0
  151. langchain_agentx/provider/anthropic.py +121 -0
  152. langchain_agentx/provider/compatible_chat_openai.py +86 -0
  153. langchain_agentx/provider/env.py +45 -0
  154. langchain_agentx/provider/model_profile.py +156 -0
  155. langchain_agentx/provider/openai.py +89 -0
  156. langchain_agentx/session/__init__.py +17 -0
  157. langchain_agentx/session/agent_session.py +320 -0
  158. langchain_agentx/session/conversation_factory.py +87 -0
  159. langchain_agentx/session/conversation_recovery.py +156 -0
  160. langchain_agentx/session/conversation_session.py +198 -0
  161. langchain_agentx/session/factory.py +143 -0
  162. langchain_agentx/session/protocol.py +25 -0
  163. langchain_agentx/task_runtime/__init__.py +113 -0
  164. langchain_agentx/task_runtime/core/__init__.py +51 -0
  165. langchain_agentx/task_runtime/core/ids.py +33 -0
  166. langchain_agentx/task_runtime/core/interfaces.py +115 -0
  167. langchain_agentx/task_runtime/core/notification_priority.py +19 -0
  168. langchain_agentx/task_runtime/core/types.py +136 -0
  169. langchain_agentx/task_runtime/integrations/__init__.py +33 -0
  170. langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
  171. langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
  172. langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
  173. langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
  174. langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
  175. langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
  176. langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
  177. langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
  178. langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
  179. langchain_agentx/task_runtime/output/__init__.py +5 -0
  180. langchain_agentx/task_runtime/output/sink.py +64 -0
  181. langchain_agentx/task_runtime/policy/__init__.py +11 -0
  182. langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
  183. langchain_agentx/task_runtime/queue/__init__.py +5 -0
  184. langchain_agentx/task_runtime/queue/in_memory.py +55 -0
  185. langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
  186. langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
  187. langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
  188. langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
  189. langchain_agentx/task_runtime/store/__init__.py +6 -0
  190. langchain_agentx/task_runtime/store/in_memory.py +81 -0
  191. langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
  192. langchain_agentx/task_runtime/tasks/__init__.py +76 -0
  193. langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
  194. langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
  195. langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
  196. langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
  197. langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
  198. langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
  199. langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
  200. langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
  201. langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
  202. langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
  203. langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
  204. langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
  205. langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
  206. langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
  207. langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
  208. langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
  209. langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
  210. langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
  211. langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
  212. langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
  213. langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
  214. langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
  215. langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
  216. langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
  217. langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
  218. langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
  219. langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
  220. langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
  221. langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
  222. langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
  223. langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
  224. langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
  225. langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
  226. langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
  227. langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
  228. langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
  229. langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
  230. langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
  231. langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
  232. langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
  233. langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
  234. langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
  235. langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
  236. langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
  237. langchain_agentx/tool_runtime/__init__.py +90 -0
  238. langchain_agentx/tool_runtime/adapter.py +365 -0
  239. langchain_agentx/tool_runtime/base.py +319 -0
  240. langchain_agentx/tool_runtime/errors.py +190 -0
  241. langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
  242. langchain_agentx/tool_runtime/loader.py +195 -0
  243. langchain_agentx/tool_runtime/models.py +260 -0
  244. langchain_agentx/tool_runtime/permission_context.py +78 -0
  245. langchain_agentx/tool_runtime/pipeline.py +621 -0
  246. langchain_agentx/tool_runtime/policy.py +447 -0
  247. langchain_agentx/tool_runtime/registry.py +81 -0
  248. langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
  249. langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
  250. langchain_agentx/tool_runtime/resolvers/background.py +32 -0
  251. langchain_agentx/tool_runtime/resolvers/base.py +20 -0
  252. langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
  253. langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
  254. langchain_agentx/tool_runtime/session_store.py +132 -0
  255. langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
  256. langchain_agentx/tool_runtime/state_bridge.py +164 -0
  257. langchain_agentx/tools/__init__.py +26 -0
  258. langchain_agentx/tools/agent/__init__.py +9 -0
  259. langchain_agentx/tools/agent/backend.py +53 -0
  260. langchain_agentx/tools/agent/built_in/__init__.py +19 -0
  261. langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
  262. langchain_agentx/tools/agent/built_in/explore.py +80 -0
  263. langchain_agentx/tools/agent/built_in/general.py +57 -0
  264. langchain_agentx/tools/agent/built_in/plan.py +89 -0
  265. langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
  266. langchain_agentx/tools/agent/built_in/verification.py +120 -0
  267. langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
  268. langchain_agentx/tools/agent/cwd_resolution.py +119 -0
  269. langchain_agentx/tools/agent/limits.py +26 -0
  270. langchain_agentx/tools/agent/loader.py +270 -0
  271. langchain_agentx/tools/agent/models.py +85 -0
  272. langchain_agentx/tools/agent/prompt.py +120 -0
  273. langchain_agentx/tools/agent/registry/__init__.py +18 -0
  274. langchain_agentx/tools/agent/registry/config.py +29 -0
  275. langchain_agentx/tools/agent/registry/registry.py +47 -0
  276. langchain_agentx/tools/agent/scope.py +137 -0
  277. langchain_agentx/tools/agent/tool.py +256 -0
  278. langchain_agentx/tools/bash/__init__.py +9 -0
  279. langchain_agentx/tools/bash/ast_security.py +571 -0
  280. langchain_agentx/tools/bash/backend.py +1447 -0
  281. langchain_agentx/tools/bash/bash_hardening.py +734 -0
  282. langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
  283. langchain_agentx/tools/bash/cwd_reporter.py +95 -0
  284. langchain_agentx/tools/bash/limits.py +71 -0
  285. langchain_agentx/tools/bash/mode_validation.py +282 -0
  286. langchain_agentx/tools/bash/models.py +131 -0
  287. langchain_agentx/tools/bash/observability.py +148 -0
  288. langchain_agentx/tools/bash/output_utils.py +200 -0
  289. langchain_agentx/tools/bash/path_security.py +2429 -0
  290. langchain_agentx/tools/bash/prompt.py +68 -0
  291. langchain_agentx/tools/bash/read_only_validation.py +589 -0
  292. langchain_agentx/tools/bash/result_presenter.py +324 -0
  293. langchain_agentx/tools/bash/sandbox_decision.py +133 -0
  294. langchain_agentx/tools/bash/security.py +311 -0
  295. langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
  296. langchain_agentx/tools/bash/sed_validation.py +163 -0
  297. langchain_agentx/tools/bash/semantics.py +111 -0
  298. langchain_agentx/tools/bash/session_manager.py +205 -0
  299. langchain_agentx/tools/bash/session_runtime.py +290 -0
  300. langchain_agentx/tools/bash/shell_locator.py +191 -0
  301. langchain_agentx/tools/bash/task_runtime.py +91 -0
  302. langchain_agentx/tools/bash/tool.py +939 -0
  303. langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
  304. langchain_agentx/tools/glob/__init__.py +9 -0
  305. langchain_agentx/tools/glob/models.py +57 -0
  306. langchain_agentx/tools/glob/pagination.py +30 -0
  307. langchain_agentx/tools/glob/prompt.py +24 -0
  308. langchain_agentx/tools/glob/rg_list_backend.py +139 -0
  309. langchain_agentx/tools/glob/rg_pattern.py +44 -0
  310. langchain_agentx/tools/glob/tool.py +327 -0
  311. langchain_agentx/tools/grep/__init__.py +7 -0
  312. langchain_agentx/tools/grep/backend.py +375 -0
  313. langchain_agentx/tools/grep/models.py +127 -0
  314. langchain_agentx/tools/grep/prompt.py +30 -0
  315. langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
  316. langchain_agentx/tools/grep/tool.py +475 -0
  317. langchain_agentx/tools/read/__init__.py +9 -0
  318. langchain_agentx/tools/read/backend.py +415 -0
  319. langchain_agentx/tools/read/limits.py +67 -0
  320. langchain_agentx/tools/read/models.py +156 -0
  321. langchain_agentx/tools/read/prompt.py +73 -0
  322. langchain_agentx/tools/read/tool.py +494 -0
  323. langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
  324. langchain_agentx/tools/skill/__init__.py +4 -0
  325. langchain_agentx/tools/skill/argument_substitution.py +80 -0
  326. langchain_agentx/tools/skill/loader.py +196 -0
  327. langchain_agentx/tools/skill/models.py +88 -0
  328. langchain_agentx/tools/skill/policy.py +80 -0
  329. langchain_agentx/tools/skill/prompt.py +35 -0
  330. langchain_agentx/tools/skill/tool.py +222 -0
  331. langchain_agentx/utils/__init__.py +0 -0
  332. langchain_agentx/utils/cwd.py +124 -0
  333. langchain_agentx/utils/host_platform.py +112 -0
  334. langchain_agentx/utils/path_hierarchy.py +48 -0
  335. langchain_agentx/utils/path_user_input.py +66 -0
  336. langchain_agentx/utils/rg_executable.py +18 -0
  337. langchain_agentx/utils/subprocess_text.py +101 -0
  338. langchain_agentx/utils/temp_paths.py +77 -0
  339. langchain_agentx/utils/unc_path.py +25 -0
  340. langchain_agentx/utils/win_reserved_paths.py +51 -0
  341. langchain_agentx/workflow/__init__.py +7 -0
  342. langchain_agentx/workflow/base.py +97 -0
  343. langchain_agentx/workflow/batch.py +55 -0
  344. langchain_agentx/workflow/dag.py +54 -0
  345. langchain_agentx/workspace/__init__.py +13 -0
  346. langchain_agentx/workspace/config.py +140 -0
  347. langchain_agentx/workspace/path_key_normalizer.py +30 -0
  348. langchain_agentx/workspace/resolver.py +74 -0
  349. langchain_agentx/workspace/validators.py +41 -0
  350. langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
  351. langchain_agentx_python-0.1.dist-info/METADATA +513 -0
  352. langchain_agentx_python-0.1.dist-info/RECORD +354 -0
  353. langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
  354. langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,81 @@
1
+ """
2
+ task_runtime/store/in_memory.py — TaskStore 内存实现
3
+
4
+ 职责:
5
+ 基于 dict 的任务记录存储;采用 RLock 保证基础并发安全。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import replace
11
+ from threading import RLock
12
+ from typing import Iterable, Optional
13
+
14
+ from ..core.interfaces import TaskStore
15
+ from ..core.types import TaskRecord, TaskStatus
16
+
17
+
18
+ class InMemoryTaskStore(TaskStore):
19
+ def __init__(self) -> None:
20
+ self._lock = RLock()
21
+ self._records: dict[str, TaskRecord] = {}
22
+
23
+ def create(self, record: TaskRecord) -> None:
24
+ with self._lock:
25
+ self._records[record.task_id] = record
26
+
27
+ def get(self, task_id: str) -> Optional[TaskRecord]:
28
+ with self._lock:
29
+ return self._records.get(task_id)
30
+
31
+ def update(self, task_id: str, patch: dict) -> Optional[TaskRecord]:
32
+ with self._lock:
33
+ current = self._records.get(task_id)
34
+ if current is None:
35
+ return None
36
+ updated = replace(current, **patch)
37
+ self._records[task_id] = updated
38
+ return updated
39
+
40
+ def list(self) -> Iterable[TaskRecord]:
41
+ with self._lock:
42
+ return list(self._records.values())
43
+
44
+ def delete(self, task_id: str) -> None:
45
+ with self._lock:
46
+ self._records.pop(task_id, None)
47
+
48
+ def evict_expired(self, *, now_ts: float, ttl_sec: float) -> int:
49
+ if ttl_sec <= 0:
50
+ return 0
51
+ terminal = {TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.STOPPED}
52
+ with self._lock:
53
+ to_delete = [
54
+ task_id
55
+ for task_id, record in self._records.items()
56
+ if record.status in terminal and (now_ts - float(record.updated_at)) >= ttl_sec
57
+ ]
58
+ for task_id in to_delete:
59
+ self._records.pop(task_id, None)
60
+ return len(to_delete)
61
+
62
+ def evict_over_capacity(self, *, max_records: int) -> int:
63
+ if max_records < 0:
64
+ return 0
65
+ terminal = {TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.STOPPED}
66
+ with self._lock:
67
+ total = len(self._records)
68
+ overflow = total - max_records
69
+ if overflow <= 0:
70
+ return 0
71
+
72
+ terminal_items = [
73
+ (task_id, record)
74
+ for task_id, record in self._records.items()
75
+ if record.status in terminal
76
+ ]
77
+ terminal_items.sort(key=lambda item: float(item[1].updated_at))
78
+ to_delete = [task_id for task_id, _ in terminal_items[:overflow]]
79
+ for task_id in to_delete:
80
+ self._records.pop(task_id, None)
81
+ return len(to_delete)
@@ -0,0 +1,281 @@
1
+ """
2
+ task_runtime/store/sqlite_store.py — Task Runtime SQLite 持久化存储
3
+
4
+ 职责:
5
+ 提供 TaskStore 的最小持久化实现:
6
+ 1. create/get/update/list/delete 的持久化读写
7
+ 2. 任务终态去重字段与元数据的序列化
8
+
9
+ 边界:
10
+ - 仅实现 TaskStore,不处理队列与 loop 注入
11
+ - 采用 sqlite3 标准库,便于本地与 CI 使用
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import asdict
17
+ import json
18
+ import sqlite3
19
+ from threading import RLock
20
+ from typing import Iterable, Optional
21
+
22
+ from ..core.interfaces import TaskStore
23
+ from ..core.types import TaskRecord, TaskScope, TaskStatus, TaskType
24
+
25
+
26
+ class SqliteTaskStore(TaskStore):
27
+ def __init__(self, db_path: str) -> None:
28
+ self._lock = RLock()
29
+ self._conn = sqlite3.connect(db_path, check_same_thread=False)
30
+ self._conn.row_factory = sqlite3.Row
31
+ self._init_schema()
32
+
33
+ def _init_schema(self) -> None:
34
+ with self._conn:
35
+ self._conn.execute(
36
+ """
37
+ CREATE TABLE IF NOT EXISTS task_records (
38
+ task_id TEXT PRIMARY KEY,
39
+ task_type TEXT NOT NULL,
40
+ status TEXT NOT NULL,
41
+ description TEXT NOT NULL,
42
+ scope_agent_id TEXT,
43
+ input_json TEXT NOT NULL,
44
+ metadata_json TEXT NOT NULL,
45
+ created_at REAL NOT NULL,
46
+ updated_at REAL NOT NULL,
47
+ output_file TEXT,
48
+ terminal_emitted_statuses_json TEXT NOT NULL,
49
+ notification_consumed INTEGER NOT NULL,
50
+ run_id TEXT,
51
+ external_ref TEXT,
52
+ lease_until_ts REAL,
53
+ last_heartbeat_ts REAL,
54
+ worker_id TEXT
55
+ )
56
+ """
57
+ )
58
+ self._migrate_task_columns()
59
+
60
+ def _migrate_task_columns(self) -> None:
61
+ cur = self._conn.execute("PRAGMA table_info(task_records)")
62
+ cols = {row[1] for row in cur.fetchall()}
63
+ alters = [
64
+ ("run_id", "TEXT"),
65
+ ("external_ref", "TEXT"),
66
+ ("lease_until_ts", "REAL"),
67
+ ("last_heartbeat_ts", "REAL"),
68
+ ("worker_id", "TEXT"),
69
+ ]
70
+ for name, sql_type in alters:
71
+ if name not in cols:
72
+ self._conn.execute(
73
+ f"ALTER TABLE task_records ADD COLUMN {name} {sql_type}"
74
+ )
75
+
76
+ def create(self, record: TaskRecord) -> None:
77
+ payload = _to_row(record)
78
+ with self._lock, self._conn:
79
+ self._conn.execute(
80
+ """
81
+ INSERT OR REPLACE INTO task_records (
82
+ task_id, task_type, status, description, scope_agent_id,
83
+ input_json, metadata_json, created_at, updated_at, output_file,
84
+ terminal_emitted_statuses_json, notification_consumed,
85
+ run_id, external_ref, lease_until_ts, last_heartbeat_ts, worker_id
86
+ ) VALUES (
87
+ :task_id, :task_type, :status, :description, :scope_agent_id,
88
+ :input_json, :metadata_json, :created_at, :updated_at, :output_file,
89
+ :terminal_emitted_statuses_json, :notification_consumed,
90
+ :run_id, :external_ref, :lease_until_ts, :last_heartbeat_ts, :worker_id
91
+ )
92
+ """,
93
+ payload,
94
+ )
95
+
96
+ def get(self, task_id: str) -> Optional[TaskRecord]:
97
+ with self._lock:
98
+ row = self._conn.execute(
99
+ "SELECT * FROM task_records WHERE task_id = ?",
100
+ (task_id,),
101
+ ).fetchone()
102
+ if row is None:
103
+ return None
104
+ return _from_row(row)
105
+
106
+ def update(self, task_id: str, patch: dict) -> Optional[TaskRecord]:
107
+ with self._lock:
108
+ current = self.get(task_id)
109
+ if current is None:
110
+ return None
111
+ data = asdict(current)
112
+ data.update(patch)
113
+ # dataclass->dict 后 enums 仍是对象,这里统一规范为 TaskRecord
114
+ updated = TaskRecord(
115
+ task_id=data["task_id"],
116
+ task_type=_coerce_task_type(data["task_type"]),
117
+ status=_coerce_task_status(data["status"]),
118
+ description=data["description"],
119
+ scope=data["scope"] if isinstance(data["scope"], TaskScope) else TaskScope(
120
+ agent_id=data["scope"].get("agent_id") if isinstance(data["scope"], dict) else None
121
+ ),
122
+ input=data.get("input") or {},
123
+ metadata=data.get("metadata") or {},
124
+ created_at=float(data["created_at"]),
125
+ updated_at=float(data["updated_at"]),
126
+ output_file=data.get("output_file"),
127
+ terminal_emitted_statuses=set(
128
+ _coerce_task_status(s) for s in (data.get("terminal_emitted_statuses") or set())
129
+ ),
130
+ notification_consumed=bool(data.get("notification_consumed", False)),
131
+ run_id=data.get("run_id"),
132
+ external_ref=data.get("external_ref"),
133
+ lease_until_ts=(
134
+ float(data["lease_until_ts"])
135
+ if data.get("lease_until_ts") is not None
136
+ else None
137
+ ),
138
+ last_heartbeat_ts=(
139
+ float(data["last_heartbeat_ts"])
140
+ if data.get("last_heartbeat_ts") is not None
141
+ else None
142
+ ),
143
+ worker_id=data.get("worker_id"),
144
+ )
145
+ self.create(updated)
146
+ return updated
147
+
148
+ def list(self) -> Iterable[TaskRecord]:
149
+ with self._lock:
150
+ rows = self._conn.execute("SELECT * FROM task_records").fetchall()
151
+ return [_from_row(row) for row in rows]
152
+
153
+ def delete(self, task_id: str) -> None:
154
+ with self._lock, self._conn:
155
+ self._conn.execute("DELETE FROM task_records WHERE task_id = ?", (task_id,))
156
+
157
+ def close(self) -> None:
158
+ with self._lock:
159
+ self._conn.close()
160
+
161
+ def evict_expired(self, *, now_ts: float, ttl_sec: float) -> int:
162
+ if ttl_sec <= 0:
163
+ return 0
164
+ cutoff = float(now_ts) - float(ttl_sec)
165
+ terminal_statuses = (
166
+ TaskStatus.COMPLETED.value,
167
+ TaskStatus.FAILED.value,
168
+ TaskStatus.STOPPED.value,
169
+ )
170
+ with self._lock, self._conn:
171
+ cur = self._conn.execute(
172
+ """
173
+ DELETE FROM task_records
174
+ WHERE status IN (?, ?, ?) AND updated_at <= ?
175
+ """,
176
+ (*terminal_statuses, cutoff),
177
+ )
178
+ return int(cur.rowcount or 0)
179
+
180
+ def evict_over_capacity(self, *, max_records: int) -> int:
181
+ if max_records < 0:
182
+ return 0
183
+ terminal_statuses = (
184
+ TaskStatus.COMPLETED.value,
185
+ TaskStatus.FAILED.value,
186
+ TaskStatus.STOPPED.value,
187
+ )
188
+ with self._lock, self._conn:
189
+ total = int(
190
+ self._conn.execute("SELECT COUNT(*) FROM task_records").fetchone()[0]
191
+ )
192
+ overflow = total - max_records
193
+ if overflow <= 0:
194
+ return 0
195
+ rows = self._conn.execute(
196
+ """
197
+ SELECT task_id
198
+ FROM task_records
199
+ WHERE status IN (?, ?, ?)
200
+ ORDER BY updated_at ASC
201
+ LIMIT ?
202
+ """,
203
+ (*terminal_statuses, overflow),
204
+ ).fetchall()
205
+ ids = [row["task_id"] for row in rows]
206
+ if not ids:
207
+ return 0
208
+ placeholders = ",".join("?" for _ in ids)
209
+ cur = self._conn.execute(
210
+ f"DELETE FROM task_records WHERE task_id IN ({placeholders})",
211
+ ids,
212
+ )
213
+ return int(cur.rowcount or 0)
214
+
215
+
216
+ def _to_row(record: TaskRecord) -> dict:
217
+ return {
218
+ "task_id": record.task_id,
219
+ "task_type": record.task_type.value,
220
+ "status": record.status.value,
221
+ "description": record.description,
222
+ "scope_agent_id": record.scope.agent_id,
223
+ "input_json": json.dumps(record.input, ensure_ascii=True),
224
+ "metadata_json": json.dumps(record.metadata, ensure_ascii=True),
225
+ "created_at": float(record.created_at),
226
+ "updated_at": float(record.updated_at),
227
+ "output_file": record.output_file,
228
+ "terminal_emitted_statuses_json": json.dumps(
229
+ [s.value for s in record.terminal_emitted_statuses], ensure_ascii=True
230
+ ),
231
+ "notification_consumed": 1 if record.notification_consumed else 0,
232
+ "run_id": record.run_id,
233
+ "external_ref": record.external_ref,
234
+ "lease_until_ts": record.lease_until_ts,
235
+ "last_heartbeat_ts": record.last_heartbeat_ts,
236
+ "worker_id": record.worker_id,
237
+ }
238
+
239
+
240
+ def _from_row(row: sqlite3.Row) -> TaskRecord:
241
+ emitted_raw = json.loads(row["terminal_emitted_statuses_json"] or "[]")
242
+ keys = row.keys()
243
+ return TaskRecord(
244
+ task_id=row["task_id"],
245
+ task_type=TaskType(row["task_type"]),
246
+ status=TaskStatus(row["status"]),
247
+ description=row["description"],
248
+ scope=TaskScope(agent_id=row["scope_agent_id"]),
249
+ input=json.loads(row["input_json"] or "{}"),
250
+ metadata=json.loads(row["metadata_json"] or "{}"),
251
+ created_at=float(row["created_at"]),
252
+ updated_at=float(row["updated_at"]),
253
+ output_file=row["output_file"],
254
+ terminal_emitted_statuses=set(TaskStatus(s) for s in emitted_raw),
255
+ notification_consumed=bool(row["notification_consumed"]),
256
+ run_id=row["run_id"] if "run_id" in keys else None,
257
+ external_ref=row["external_ref"] if "external_ref" in keys else None,
258
+ lease_until_ts=(
259
+ float(row["lease_until_ts"])
260
+ if "lease_until_ts" in keys and row["lease_until_ts"] is not None
261
+ else None
262
+ ),
263
+ last_heartbeat_ts=(
264
+ float(row["last_heartbeat_ts"])
265
+ if "last_heartbeat_ts" in keys and row["last_heartbeat_ts"] is not None
266
+ else None
267
+ ),
268
+ worker_id=row["worker_id"] if "worker_id" in keys else None,
269
+ )
270
+
271
+
272
+ def _coerce_task_type(value) -> TaskType:
273
+ if isinstance(value, TaskType):
274
+ return value
275
+ return TaskType(str(value))
276
+
277
+
278
+ def _coerce_task_status(value) -> TaskStatus:
279
+ if isinstance(value, TaskStatus):
280
+ return value
281
+ return TaskStatus(str(value))
@@ -0,0 +1,76 @@
1
+ """
2
+ task_runtime/tasks/__init__.py — 按 TaskType 分组的语义与执行器导出
3
+
4
+ 职责:
5
+ 为上层提供 ``TaskExecutor`` 实现与契约类型,隐藏子包细节。
6
+
7
+ 与 CC 对比:
8
+ CC ``src/tasks/*.ts`` 按文件组织;本仓库按 ``TaskType`` 枚举分目录,
9
+ 每目录内 OOP:``spec`` / ``executor`` / ``notification`` / ``semantics``。
10
+ """
11
+
12
+ from .custom import CUSTOM_SEMANTIC, CustomTaskExecutor, CustomTaskInput
13
+ from .dream_task import (
14
+ DREAM_TASK_SEMANTIC,
15
+ DreamTaskExecutor,
16
+ DreamTaskInput,
17
+ DreamTaskNotificationFormatter,
18
+ DreamTaskState,
19
+ )
20
+ from .in_process_teammate import (
21
+ IN_PROCESS_TEAMMATE_SEMANTIC,
22
+ InProcessTeammateExecutor,
23
+ InProcessTeammateTaskInput,
24
+ TeammateIdentity,
25
+ )
26
+ from .local_agent import (
27
+ LOCAL_AGENT_SEMANTIC,
28
+ DefaultLocalAgentRunner,
29
+ LocalAgentExecutor,
30
+ LocalAgentRunner,
31
+ LocalAgentTaskInput,
32
+ )
33
+ from .local_bash import (
34
+ LOCAL_BASH_SEMANTIC,
35
+ LocalBashExecutor,
36
+ LocalBashNotificationFormatter,
37
+ LocalBashTaskInput,
38
+ )
39
+ from .remote_agent import (
40
+ REMOTE_AGENT_SEMANTIC,
41
+ ImmediateRemoteAgentBackend,
42
+ RecordingRemoteAgentBackend,
43
+ RemoteAgentExecutor,
44
+ RemoteAgentSessionBackend,
45
+ RemoteAgentTaskInput,
46
+ )
47
+
48
+ __all__ = [
49
+ "LOCAL_BASH_SEMANTIC",
50
+ "LocalBashExecutor",
51
+ "LocalBashNotificationFormatter",
52
+ "LocalBashTaskInput",
53
+ "LOCAL_AGENT_SEMANTIC",
54
+ "DefaultLocalAgentRunner",
55
+ "LocalAgentExecutor",
56
+ "LocalAgentRunner",
57
+ "LocalAgentTaskInput",
58
+ "IN_PROCESS_TEAMMATE_SEMANTIC",
59
+ "InProcessTeammateExecutor",
60
+ "InProcessTeammateTaskInput",
61
+ "TeammateIdentity",
62
+ "REMOTE_AGENT_SEMANTIC",
63
+ "ImmediateRemoteAgentBackend",
64
+ "RecordingRemoteAgentBackend",
65
+ "RemoteAgentExecutor",
66
+ "RemoteAgentSessionBackend",
67
+ "RemoteAgentTaskInput",
68
+ "CUSTOM_SEMANTIC",
69
+ "CustomTaskExecutor",
70
+ "CustomTaskInput",
71
+ "DREAM_TASK_SEMANTIC",
72
+ "DreamTaskExecutor",
73
+ "DreamTaskInput",
74
+ "DreamTaskNotificationFormatter",
75
+ "DreamTaskState",
76
+ ]
@@ -0,0 +1,15 @@
1
+ """后台 AI 分析任务导出。"""
2
+
3
+ from .base import BackgroundAiTask
4
+ from .evaluation import EvaluationAnalysisTask
5
+ from .registry import BackgroundAiTaskRegistry, registry
6
+
7
+ # OB-D10:侧载 trace.cleanup 注册(executor 模块尾 registry.register)
8
+ from ..trace_cleanup import executor as _trace_cleanup_executor # noqa: F401
9
+
10
+ __all__ = [
11
+ "BackgroundAiTask",
12
+ "BackgroundAiTaskRegistry",
13
+ "registry",
14
+ "EvaluationAnalysisTask",
15
+ ]
@@ -0,0 +1,41 @@
1
+ """
2
+ task_runtime/tasks/ai_analysis/base.py — 后台 AI 分析任务基类。
3
+
4
+ 职责:
5
+ 继承 BaseWorkflow,覆盖 container_type 为 background_task,
6
+ run() 增加静默降级,避免影响主流程。
7
+
8
+ 链路位置:
9
+ 由后台任务调度器通过队列轮询触发。
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from abc import abstractmethod
16
+ from typing import Any
17
+
18
+ from ....workflow.base import BaseWorkflow
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class BackgroundAiTask(BaseWorkflow):
24
+ """后台 AI 分析任务基类。失败必须静默降级。"""
25
+
26
+ container_type: str = "background_task"
27
+
28
+ async def run(self, **kwargs: Any) -> Any:
29
+ """覆盖 BaseWorkflow.run(),增加静默降级。"""
30
+ try:
31
+ return await super().run(**kwargs)
32
+ except Exception:
33
+ logger.exception("%s 执行失败,静默降级", self.__class__.__name__)
34
+ return None
35
+
36
+ @abstractmethod
37
+ async def _execute(self, **kwargs: Any) -> Any:
38
+ """子类实现具体分析逻辑,结果写回外部存储。"""
39
+
40
+
41
+ __all__ = ["BackgroundAiTask"]
@@ -0,0 +1,67 @@
1
+ """
2
+ task_runtime/tasks/ai_analysis/evaluation.py — EvaluationAnalysisTask 内置实现。
3
+
4
+ 职责:
5
+ 对 EvaluationStore 中 check_failed 的 session 进行深度 AI 分析,
6
+ 并将结果写回 EvaluationStore.diagnostic_reports。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from langchain_core.messages import HumanMessage
14
+
15
+ from ....observability.evaluation.store import DiagnosticReport, EvaluationStore
16
+ from .base import BackgroundAiTask
17
+ from .registry import registry
18
+
19
+
20
+ class EvaluationAnalysisTask(BackgroundAiTask):
21
+ """对 check_failed session 进行深度 AI 分析。"""
22
+
23
+ def __init__(self, evaluation_store: EvaluationStore, **workflow_kwargs: Any) -> None:
24
+ super().__init__(**workflow_kwargs)
25
+ self._evaluation_store = evaluation_store
26
+
27
+ async def _execute(self, session_id: str, **_: Any) -> None:
28
+ failures = self._evaluation_store.get_results(session_id)
29
+ fail_results = [result for result in failures if result.status == "fail"]
30
+ if not fail_results:
31
+ return
32
+
33
+ evidence_summary = "\n".join(
34
+ f"- [{result.checker_name}] {result.message} evidence={result.evidence}"
35
+ for result in fail_results
36
+ )
37
+ prompt = (
38
+ f"以下是 session {session_id} 的评估失败记录:\n"
39
+ f"{evidence_summary}\n\n"
40
+ "请分析根因,指出是哪个模块、哪个逻辑路径导致了这些问题。\n"
41
+ "输出格式:根因 / 涉及模块 / 建议排查点"
42
+ )
43
+
44
+ result = await self._invoke_agent(
45
+ messages=[HumanMessage(content=prompt)],
46
+ task_key=session_id,
47
+ )
48
+
49
+ report_text = ""
50
+ if isinstance(result, dict):
51
+ messages = result.get("messages", [])
52
+ if messages:
53
+ report_text = str(getattr(messages[-1], "content", "") or "")
54
+
55
+ self._evaluation_store.save_diagnostic_report(
56
+ DiagnosticReport(
57
+ session_id=session_id,
58
+ checker_names=[result.checker_name for result in fail_results],
59
+ report=report_text,
60
+ model="agent",
61
+ )
62
+ )
63
+
64
+
65
+ registry.register("evaluation.check_failed", EvaluationAnalysisTask)
66
+
67
+ __all__ = ["EvaluationAnalysisTask"]
@@ -0,0 +1,36 @@
1
+ """
2
+ task_runtime/tasks/ai_analysis/registry.py — 后台 AI 任务注册表。
3
+
4
+ 职责:
5
+ 维护 trigger -> task_factory 映射,支持内置注册与外部覆盖。
6
+
7
+ 链路位置:
8
+ 调度器通过 registry.get(trigger) 获取任务工厂。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Callable
14
+
15
+
16
+ class BackgroundAiTaskRegistry:
17
+ """trigger -> task_factory 注册表。"""
18
+
19
+ def __init__(self) -> None:
20
+ self._registry: dict[str, Callable[..., Any]] = {}
21
+
22
+ def register(self, trigger: str, task_factory: Callable[..., Any]) -> None:
23
+ """注册或覆盖触发条件对应的任务工厂。"""
24
+ self._registry[trigger] = task_factory
25
+
26
+ def get(self, trigger: str) -> Callable[..., Any] | None:
27
+ """按触发条件获取任务工厂,未注册返回 None。"""
28
+ return self._registry.get(trigger)
29
+
30
+ def list_triggers(self) -> list[str]:
31
+ return list(self._registry.keys())
32
+
33
+
34
+ registry = BackgroundAiTaskRegistry()
35
+
36
+ __all__ = ["BackgroundAiTaskRegistry", "registry"]
@@ -0,0 +1,70 @@
1
+ """
2
+ task_runtime/tasks/ai_analysis/scheduler.py — BackgroundAiTaskScheduler。
3
+
4
+ 职责:
5
+ 定时轮询 EvaluationStore,找出有 fail 且无诊断报告的 session,
6
+ 并通过 registry 获取任务工厂异步触发分析任务。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import logging
13
+ from typing import Any
14
+
15
+ from ....observability.evaluation.store import EvaluationStore
16
+ from .registry import BackgroundAiTaskRegistry
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class BackgroundAiTaskScheduler:
22
+ """定时轮询 EvaluationStore,触发后台 AI 分析任务。"""
23
+
24
+ def __init__(
25
+ self,
26
+ evaluation_store: EvaluationStore,
27
+ registry: BackgroundAiTaskRegistry,
28
+ task_factory_kwargs: dict[str, Any] | None = None,
29
+ poll_interval: float = 60.0,
30
+ ) -> None:
31
+ self._store = evaluation_store
32
+ self._registry = registry
33
+ self._task_factory_kwargs = task_factory_kwargs or {}
34
+ self._poll_interval = poll_interval
35
+ self._running = False
36
+
37
+ async def start(self) -> None:
38
+ """启动轮询循环,直到 stop() 被调用。"""
39
+ self._running = True
40
+ logger.info("BackgroundAiTaskScheduler 启动,轮询间隔 %ss", self._poll_interval)
41
+ while self._running:
42
+ await self._poll_once()
43
+ await asyncio.sleep(self._poll_interval)
44
+
45
+ def stop(self) -> None:
46
+ self._running = False
47
+
48
+ async def _poll_once(self) -> None:
49
+ try:
50
+ session_ids = self._store.get_sessions_needing_analysis()
51
+ except Exception:
52
+ logger.exception("Scheduler 轮询 EvaluationStore 失败")
53
+ return
54
+
55
+ for session_id in session_ids:
56
+ task_factory = self._registry.get("evaluation.check_failed")
57
+ if task_factory is None:
58
+ continue
59
+ try:
60
+ task = task_factory(
61
+ evaluation_store=self._store,
62
+ **self._task_factory_kwargs,
63
+ )
64
+ asyncio.create_task(task.run(session_id=session_id))
65
+ logger.debug("Scheduler 已触发 session %s 的分析任务", session_id)
66
+ except Exception:
67
+ logger.exception("Scheduler 触发 session %s 分析任务失败", session_id)
68
+
69
+
70
+ __all__ = ["BackgroundAiTaskScheduler"]
@@ -0,0 +1,6 @@
1
+ """Base semantic contracts for task types."""
2
+
3
+ from .contracts import TaskSemanticContract
4
+
5
+ __all__ = ["TaskSemanticContract"]
6
+