PraisonAI 3.0.0__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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,484 @@
1
+ """
2
+ Queue Scheduler for PraisonAI.
3
+
4
+ Priority-based FIFO scheduler with concurrency limits.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ import time
10
+ import uuid
11
+ from collections import defaultdict
12
+ from typing import Any, Callable, Dict, List, Optional, Set
13
+
14
+ from .models import QueuedRun, RunState, RunPriority, QueueConfig, QueueEvent
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class QueueFullError(Exception):
20
+ """Raised when queue is at capacity."""
21
+ pass
22
+
23
+
24
+ class RunNotFoundError(Exception):
25
+ """Raised when a run is not found."""
26
+ pass
27
+
28
+
29
+ class QueueScheduler:
30
+ """
31
+ Priority-based FIFO scheduler with concurrency limits.
32
+
33
+ Runs are organized by priority (URGENT > HIGH > NORMAL > LOW),
34
+ with FIFO ordering within each priority level.
35
+ """
36
+
37
+ def __init__(self, config: Optional[QueueConfig] = None):
38
+ """
39
+ Initialize scheduler.
40
+
41
+ Args:
42
+ config: Queue configuration. Uses defaults if not provided.
43
+ """
44
+ self.config = config or QueueConfig()
45
+
46
+ # Priority queues: priority -> list of runs (FIFO order)
47
+ self._queues: Dict[RunPriority, List[QueuedRun]] = {
48
+ p: [] for p in RunPriority
49
+ }
50
+
51
+ # Running runs: run_id -> run
52
+ self._running: Dict[str, QueuedRun] = {}
53
+
54
+ # All runs by ID for quick lookup
55
+ self._all_runs: Dict[str, QueuedRun] = {}
56
+
57
+ # Lock for thread safety
58
+ self._lock = asyncio.Lock()
59
+
60
+ # Event callbacks
61
+ self._event_callbacks: List[Callable[[QueueEvent], None]] = []
62
+
63
+ # Cancellation tokens
64
+ self._cancel_tokens: Set[str] = set()
65
+
66
+ def add_event_callback(self, callback: Callable[[QueueEvent], None]) -> None:
67
+ """Add a callback for queue events."""
68
+ self._event_callbacks.append(callback)
69
+
70
+ def remove_event_callback(self, callback: Callable[[QueueEvent], None]) -> None:
71
+ """Remove an event callback."""
72
+ if callback in self._event_callbacks:
73
+ self._event_callbacks.remove(callback)
74
+
75
+ def _emit_event(self, event: QueueEvent) -> None:
76
+ """Emit an event to all callbacks."""
77
+ for callback in self._event_callbacks:
78
+ try:
79
+ callback(event)
80
+ except Exception as e:
81
+ logger.error(f"Error in event callback: {e}")
82
+
83
+ async def submit(
84
+ self,
85
+ run: QueuedRun,
86
+ check_duplicate: bool = True,
87
+ ) -> str:
88
+ """
89
+ Submit a run to the queue.
90
+
91
+ Args:
92
+ run: The run to submit.
93
+ check_duplicate: If True, reject duplicate run_ids.
94
+
95
+ Returns:
96
+ The run_id.
97
+
98
+ Raises:
99
+ QueueFullError: If queue is at capacity.
100
+ ValueError: If run_id already exists (when check_duplicate=True).
101
+ """
102
+ async with self._lock:
103
+ # Check for duplicates
104
+ if check_duplicate and run.run_id in self._all_runs:
105
+ raise ValueError(f"Run {run.run_id} already exists")
106
+
107
+ # Check queue capacity
108
+ total_queued = sum(len(q) for q in self._queues.values())
109
+ if total_queued >= self.config.max_queue_size:
110
+ raise QueueFullError(
111
+ f"Queue is full ({total_queued}/{self.config.max_queue_size})"
112
+ )
113
+
114
+ # Ensure state is QUEUED
115
+ run.state = RunState.QUEUED
116
+
117
+ # Add to appropriate priority queue
118
+ self._queues[run.priority].append(run)
119
+ self._all_runs[run.run_id] = run
120
+
121
+ logger.debug(f"Submitted run {run.run_id} with priority {run.priority.name}")
122
+
123
+ self._emit_event(QueueEvent(
124
+ event_type="run_submitted",
125
+ run_id=run.run_id,
126
+ data={"priority": run.priority.name, "agent": run.agent_name}
127
+ ))
128
+
129
+ return run.run_id
130
+
131
+ async def next(self) -> Optional[QueuedRun]:
132
+ """
133
+ Get the next run to execute, respecting priority and concurrency limits.
134
+
135
+ Returns:
136
+ The next run, or None if no run is available.
137
+ """
138
+ async with self._lock:
139
+ if not self._can_start_new():
140
+ return None
141
+
142
+ # Check priorities in order (URGENT first = highest value)
143
+ for priority in reversed(list(RunPriority)):
144
+ queue = self._queues[priority]
145
+
146
+ for i, run in enumerate(queue):
147
+ if self._can_run(run):
148
+ # Remove from queue
149
+ queue.pop(i)
150
+
151
+ # Update state
152
+ run.state = RunState.RUNNING
153
+ run.started_at = time.time()
154
+
155
+ # Add to running
156
+ self._running[run.run_id] = run
157
+
158
+ logger.debug(f"Starting run {run.run_id}")
159
+
160
+ self._emit_event(QueueEvent(
161
+ event_type="run_started",
162
+ run_id=run.run_id,
163
+ data={"agent": run.agent_name}
164
+ ))
165
+
166
+ return run
167
+
168
+ return None
169
+
170
+ async def complete(
171
+ self,
172
+ run_id: str,
173
+ output: Optional[str] = None,
174
+ metrics: Optional[Dict[str, Any]] = None,
175
+ ) -> Optional[QueuedRun]:
176
+ """
177
+ Mark a run as completed successfully.
178
+
179
+ Args:
180
+ run_id: The run ID.
181
+ output: The output content.
182
+ metrics: Optional metrics.
183
+
184
+ Returns:
185
+ The completed run, or None if not found.
186
+ """
187
+ async with self._lock:
188
+ run = self._running.pop(run_id, None)
189
+ if run is None:
190
+ logger.warning(f"Run {run_id} not found in running")
191
+ return None
192
+
193
+ run.state = RunState.SUCCEEDED
194
+ run.ended_at = time.time()
195
+ run.output_content = output
196
+ if metrics:
197
+ run.metrics.update(metrics)
198
+
199
+ logger.debug(f"Completed run {run_id}")
200
+
201
+ self._emit_event(QueueEvent(
202
+ event_type="run_completed",
203
+ run_id=run_id,
204
+ data={"duration": run.duration_seconds}
205
+ ))
206
+
207
+ return run
208
+
209
+ async def fail(
210
+ self,
211
+ run_id: str,
212
+ error: str,
213
+ metrics: Optional[Dict[str, Any]] = None,
214
+ ) -> Optional[QueuedRun]:
215
+ """
216
+ Mark a run as failed.
217
+
218
+ Args:
219
+ run_id: The run ID.
220
+ error: The error message.
221
+ metrics: Optional metrics.
222
+
223
+ Returns:
224
+ The failed run, or None if not found.
225
+ """
226
+ async with self._lock:
227
+ run = self._running.pop(run_id, None)
228
+ if run is None:
229
+ logger.warning(f"Run {run_id} not found in running")
230
+ return None
231
+
232
+ run.state = RunState.FAILED
233
+ run.ended_at = time.time()
234
+ run.error = error
235
+ if metrics:
236
+ run.metrics.update(metrics)
237
+
238
+ logger.debug(f"Failed run {run_id}: {error}")
239
+
240
+ self._emit_event(QueueEvent(
241
+ event_type="run_failed",
242
+ run_id=run_id,
243
+ data={"error": error}
244
+ ))
245
+
246
+ return run
247
+
248
+ async def cancel(self, run_id: str) -> bool:
249
+ """
250
+ Cancel a queued or running run.
251
+
252
+ Args:
253
+ run_id: The run ID to cancel.
254
+
255
+ Returns:
256
+ True if cancelled, False if not found.
257
+ """
258
+ async with self._lock:
259
+ # Check if running
260
+ if run_id in self._running:
261
+ run = self._running.pop(run_id)
262
+ run.state = RunState.CANCELLED
263
+ run.ended_at = time.time()
264
+ self._cancel_tokens.add(run_id)
265
+
266
+ logger.debug(f"Cancelled running run {run_id}")
267
+
268
+ self._emit_event(QueueEvent(
269
+ event_type="run_cancelled",
270
+ run_id=run_id,
271
+ data={"was_running": True}
272
+ ))
273
+
274
+ return True
275
+
276
+ # Check if queued
277
+ for priority in RunPriority:
278
+ queue = self._queues[priority]
279
+ for i, run in enumerate(queue):
280
+ if run.run_id == run_id:
281
+ queue.pop(i)
282
+ run.state = RunState.CANCELLED
283
+ run.ended_at = time.time()
284
+
285
+ logger.debug(f"Cancelled queued run {run_id}")
286
+
287
+ self._emit_event(QueueEvent(
288
+ event_type="run_cancelled",
289
+ run_id=run_id,
290
+ data={"was_running": False}
291
+ ))
292
+
293
+ return True
294
+
295
+ return False
296
+
297
+ def is_cancelled(self, run_id: str) -> bool:
298
+ """Check if a run has been cancelled."""
299
+ return run_id in self._cancel_tokens
300
+
301
+ def clear_cancel_token(self, run_id: str) -> None:
302
+ """Clear a cancellation token."""
303
+ self._cancel_tokens.discard(run_id)
304
+
305
+ async def retry(self, run_id: str) -> Optional[str]:
306
+ """
307
+ Retry a failed run.
308
+
309
+ Creates a new run with incremented retry count and link to parent.
310
+
311
+ Args:
312
+ run_id: The run ID to retry.
313
+
314
+ Returns:
315
+ The new run_id, or None if retry not allowed.
316
+ """
317
+ async with self._lock:
318
+ # Find the original run
319
+ original = self._all_runs.get(run_id)
320
+ if original is None:
321
+ logger.warning(f"Run {run_id} not found for retry")
322
+ return None
323
+
324
+ if not original.can_retry():
325
+ logger.warning(
326
+ f"Run {run_id} cannot be retried "
327
+ f"(state={original.state}, retries={original.retry_count}/{original.max_retries})"
328
+ )
329
+ return None
330
+
331
+ # Create new run
332
+ new_run = QueuedRun(
333
+ run_id=str(uuid.uuid4())[:8],
334
+ agent_name=original.agent_name,
335
+ input_content=original.input_content,
336
+ state=RunState.QUEUED,
337
+ priority=original.priority,
338
+ session_id=original.session_id,
339
+ trace_id=original.trace_id,
340
+ workspace=original.workspace,
341
+ user_id=original.user_id,
342
+ retry_count=original.retry_count + 1,
343
+ max_retries=original.max_retries,
344
+ parent_run_id=original.run_id,
345
+ config=original.config.copy(),
346
+ )
347
+
348
+ # Submit outside lock to avoid deadlock
349
+ await self.submit(new_run, check_duplicate=True)
350
+
351
+ logger.debug(f"Retrying run {run_id} as {new_run.run_id}")
352
+
353
+ self._emit_event(QueueEvent(
354
+ event_type="run_retried",
355
+ run_id=new_run.run_id,
356
+ data={"parent_run_id": run_id, "retry_count": new_run.retry_count}
357
+ ))
358
+
359
+ return new_run.run_id
360
+
361
+ async def pause(self, run_id: str) -> bool:
362
+ """
363
+ Pause a running run.
364
+
365
+ Note: The worker must check for pause state and handle accordingly.
366
+ """
367
+ async with self._lock:
368
+ if run_id not in self._running:
369
+ return False
370
+
371
+ run = self._running[run_id]
372
+ run.state = RunState.PAUSED
373
+
374
+ self._emit_event(QueueEvent(
375
+ event_type="run_paused",
376
+ run_id=run_id,
377
+ ))
378
+
379
+ return True
380
+
381
+ async def resume(self, run_id: str) -> bool:
382
+ """Resume a paused run."""
383
+ async with self._lock:
384
+ if run_id not in self._running:
385
+ return False
386
+
387
+ run = self._running[run_id]
388
+ if run.state != RunState.PAUSED:
389
+ return False
390
+
391
+ run.state = RunState.RUNNING
392
+
393
+ self._emit_event(QueueEvent(
394
+ event_type="run_resumed",
395
+ run_id=run_id,
396
+ ))
397
+
398
+ return True
399
+
400
+ def get_run(self, run_id: str) -> Optional[QueuedRun]:
401
+ """Get a run by ID."""
402
+ return self._all_runs.get(run_id)
403
+
404
+ def get_queued(self) -> List[QueuedRun]:
405
+ """Get all queued runs in priority order."""
406
+ result = []
407
+ for priority in reversed(list(RunPriority)):
408
+ result.extend(self._queues[priority])
409
+ return result
410
+
411
+ def get_running(self) -> List[QueuedRun]:
412
+ """Get all running runs."""
413
+ return list(self._running.values())
414
+
415
+ def get_all(self) -> List[QueuedRun]:
416
+ """Get all runs."""
417
+ return list(self._all_runs.values())
418
+
419
+ @property
420
+ def queued_count(self) -> int:
421
+ """Number of queued runs."""
422
+ return sum(len(q) for q in self._queues.values())
423
+
424
+ @property
425
+ def running_count(self) -> int:
426
+ """Number of running runs."""
427
+ return len(self._running)
428
+
429
+ def _can_start_new(self) -> bool:
430
+ """Check if we can start a new run (global limit)."""
431
+ return len(self._running) < self.config.max_concurrent_global
432
+
433
+ def _can_run(self, run: QueuedRun) -> bool:
434
+ """Check if a specific run can start (per-agent and per-workspace limits)."""
435
+ # Count running by agent
436
+ agent_count = sum(
437
+ 1 for r in self._running.values()
438
+ if r.agent_name == run.agent_name
439
+ )
440
+ if agent_count >= self.config.max_concurrent_per_agent:
441
+ return False
442
+
443
+ # Count running by workspace
444
+ if run.workspace:
445
+ workspace_count = sum(
446
+ 1 for r in self._running.values()
447
+ if r.workspace == run.workspace
448
+ )
449
+ if workspace_count >= self.config.max_concurrent_per_workspace:
450
+ return False
451
+
452
+ return True
453
+
454
+ async def clear_queue(self) -> int:
455
+ """Clear all queued (not running) runs."""
456
+ async with self._lock:
457
+ count = 0
458
+ for priority in RunPriority:
459
+ count += len(self._queues[priority])
460
+ for run in self._queues[priority]:
461
+ run.state = RunState.CANCELLED
462
+ del self._all_runs[run.run_id]
463
+ self._queues[priority] = []
464
+
465
+ return count
466
+
467
+ def load_runs(self, runs: List[QueuedRun]) -> None:
468
+ """
469
+ Load runs (e.g., from persistence after restart).
470
+
471
+ Queued runs are added to queues, running runs are re-queued.
472
+ """
473
+ for run in runs:
474
+ if run.state == RunState.RUNNING:
475
+ # Re-queue running runs (they were interrupted)
476
+ run.state = RunState.QUEUED
477
+ run.started_at = None
478
+
479
+ if run.state == RunState.QUEUED:
480
+ self._queues[run.priority].append(run)
481
+
482
+ self._all_runs[run.run_id] = run
483
+
484
+ logger.info(f"Loaded {len(runs)} runs from persistence")