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,552 @@
1
+ """
2
+ Agent Scheduler for PraisonAI - Run agents periodically 24/7.
3
+
4
+ This module provides scheduling capabilities for running PraisonAI agents
5
+ at regular intervals, enabling 24/7 autonomous agent operations.
6
+ """
7
+
8
+ import threading
9
+ import time
10
+ import logging
11
+ from datetime import datetime
12
+ from typing import Optional, Dict, Any, Callable
13
+
14
+ from .base import ScheduleParser, PraisonAgentExecutor
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class AgentScheduler:
20
+ """
21
+ Scheduler for running PraisonAI agents periodically.
22
+
23
+ Features:
24
+ - Interval-based scheduling (hourly, daily, custom)
25
+ - Thread-safe operation
26
+ - Automatic retry on failure
27
+ - Execution logging and monitoring
28
+ - Graceful shutdown
29
+
30
+ Example:
31
+ scheduler = AgentScheduler(agent, task="Check news")
32
+ scheduler.start(schedule_expr="hourly", max_retries=3)
33
+ # Agent runs every hour automatically
34
+ scheduler.stop() # Stop when needed
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ agent,
40
+ task: str,
41
+ config: Optional[Dict[str, Any]] = None,
42
+ on_success: Optional[Callable] = None,
43
+ on_failure: Optional[Callable] = None,
44
+ timeout: Optional[int] = None,
45
+ max_cost: Optional[float] = 1.00
46
+ ):
47
+ """
48
+ Initialize agent scheduler.
49
+
50
+ Args:
51
+ agent: PraisonAI Agent instance
52
+ task: Task description to execute
53
+ config: Optional configuration dict
54
+ on_success: Callback function on successful execution
55
+ on_failure: Callback function on failed execution
56
+ timeout: Maximum execution time per run in seconds (None = no limit)
57
+ max_cost: Maximum total cost in USD (default: $1.00 for safety)
58
+ """
59
+ self.agent = agent
60
+ self.task = task
61
+ self.config = config or {}
62
+ self.on_success = on_success
63
+ self.on_failure = on_failure
64
+ self.timeout = timeout
65
+ self.max_cost = max_cost
66
+
67
+ self.is_running = False
68
+ self._stop_event = threading.Event()
69
+ self._thread = None
70
+ self._executor = PraisonAgentExecutor(agent)
71
+ self._execution_count = 0
72
+ self._success_count = 0
73
+ self._failure_count = 0
74
+ self._total_cost = 0.0
75
+ self._start_time = None
76
+
77
+ def start(
78
+ self,
79
+ schedule_expr: str,
80
+ max_retries: int = 3,
81
+ run_immediately: bool = False
82
+ ) -> bool:
83
+ """
84
+ Start scheduled agent execution.
85
+
86
+ Args:
87
+ schedule_expr: Schedule expression (e.g., "hourly", "*/6h", "3600")
88
+ max_retries: Maximum retry attempts on failure
89
+ run_immediately: If True, run agent immediately before starting schedule
90
+
91
+ Returns:
92
+ True if scheduler started successfully
93
+ """
94
+ if self.is_running:
95
+ logger.warning("Scheduler is already running")
96
+ return False
97
+
98
+ try:
99
+ interval = ScheduleParser.parse(schedule_expr)
100
+ self.is_running = True
101
+ self._stop_event.clear()
102
+
103
+ logger.debug(f"Starting agent scheduler: {getattr(self.agent, 'name', 'Agent')}")
104
+ logger.debug(f"Task: {self.task}")
105
+ logger.debug(f"Schedule: {schedule_expr} ({interval}s interval)")
106
+ self.is_running = True
107
+ self._stop_event.clear()
108
+ self._start_time = datetime.now()
109
+
110
+ # Run immediately if requested
111
+ if run_immediately:
112
+ logger.debug("Running agent immediately before starting schedule...")
113
+ self._execute_with_retry(max_retries)
114
+
115
+ self._thread = threading.Thread(
116
+ target=self._run_schedule,
117
+ args=(interval, max_retries),
118
+ daemon=True
119
+ )
120
+ self._thread.start()
121
+
122
+ logger.debug("Agent scheduler started successfully")
123
+ if self.timeout:
124
+ logger.info(f"Timeout per execution: {self.timeout}s")
125
+ if self.max_cost:
126
+ logger.info(f"Budget limit: ${self.max_cost}")
127
+ return True
128
+
129
+ except Exception as e:
130
+ logger.error(f"Failed to start scheduler: {e}")
131
+ self.is_running = False
132
+ return False
133
+
134
+ def stop(self) -> bool:
135
+ """
136
+ Stop the scheduler gracefully.
137
+
138
+ Returns:
139
+ True if stopped successfully
140
+ """
141
+ if not self.is_running:
142
+ logger.debug("Scheduler is not running")
143
+ return True
144
+
145
+ logger.debug("Stopping agent scheduler...")
146
+ self._stop_event.set()
147
+
148
+ if self._thread and self._thread.is_alive():
149
+ self._thread.join(timeout=10)
150
+
151
+ self.is_running = False
152
+ logger.debug("Agent scheduler stopped")
153
+ logger.debug(f"Execution stats - Total: {self._execution_count}, Success: {self._success_count}, Failed: {self._failure_count}")
154
+ return True
155
+
156
+ def get_stats(self) -> Dict[str, Any]:
157
+ """
158
+ Get execution statistics.
159
+
160
+ Returns:
161
+ Dictionary with execution stats including cost
162
+ """
163
+ runtime = (datetime.now() - self._start_time).total_seconds() if self._start_time else 0
164
+ return {
165
+ "is_running": self.is_running,
166
+ "total_executions": self._execution_count,
167
+ "successful_executions": self._success_count,
168
+ "failed_executions": self._failure_count,
169
+ "success_rate": (self._success_count / self._execution_count * 100) if self._execution_count > 0 else 0,
170
+ "total_cost_usd": round(self._total_cost, 4),
171
+ "runtime_seconds": round(runtime, 1),
172
+ "cost_per_execution": round(self._total_cost / self._execution_count, 4) if self._execution_count > 0 else 0
173
+ }
174
+
175
+ def _update_state_if_daemon(self):
176
+ """Update state file with execution stats if running as daemon."""
177
+ try:
178
+ import os
179
+ # Check if we're running as a daemon by looking for state file
180
+ state_dir = os.path.expanduser("~/.praisonai/schedulers")
181
+ if not os.path.exists(state_dir):
182
+ return
183
+
184
+ # Try to find our state file by checking all state files for matching PID
185
+ current_pid = os.getpid()
186
+ for state_file in os.listdir(state_dir):
187
+ if not state_file.endswith('.json'):
188
+ continue
189
+
190
+ state_path = os.path.join(state_dir, state_file)
191
+ try:
192
+ import json
193
+ with open(state_path, 'r') as f:
194
+ state = json.load(f)
195
+
196
+ # Check if this is our state file
197
+ if state.get('pid') == current_pid:
198
+ # Update execution stats
199
+ state['executions'] = self._execution_count
200
+ state['cost'] = round(self._total_cost, 4)
201
+
202
+ # Write back
203
+ with open(state_path, 'w') as f:
204
+ json.dump(state, f, indent=2)
205
+ break
206
+ except Exception:
207
+ continue
208
+ except Exception as e:
209
+ # Silently fail - don't break scheduler if state update fails
210
+ logger.debug(f"Failed to update state: {e}")
211
+
212
+ def _run_schedule(self, interval: int, max_retries: int):
213
+ """Internal method to run scheduled agent executions."""
214
+ while not self._stop_event.is_set():
215
+ # Check budget limit
216
+ if self.max_cost and self._total_cost >= self.max_cost:
217
+ logger.warning(f"Budget limit reached: ${self._total_cost:.4f} >= ${self.max_cost}")
218
+ logger.warning("Stopping scheduler to prevent additional costs")
219
+ self.stop()
220
+ break
221
+
222
+ logger.debug(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Starting scheduled agent execution")
223
+
224
+ self._execute_with_retry(max_retries)
225
+
226
+ # Wait for next scheduled time
227
+ logger.debug(f"Next execution in {interval} seconds ({interval/3600:.1f} hours)")
228
+ if self.max_cost:
229
+ remaining = self.max_cost - self._total_cost
230
+ logger.debug(f"Budget remaining: ${remaining:.4f}")
231
+ self._stop_event.wait(interval)
232
+
233
+ def _execute_with_retry(self, max_retries: int):
234
+ """Execute agent with retry logic and timeout."""
235
+ self._execution_count += 1
236
+ success = False
237
+ result = None
238
+
239
+ for attempt in range(max_retries):
240
+ try:
241
+ logger.debug(f"Attempt {attempt + 1}/{max_retries}")
242
+
243
+ # Execute with timeout if specified
244
+ if self.timeout:
245
+ import signal
246
+
247
+ def timeout_handler(signum, frame):
248
+ raise TimeoutError(f"Execution exceeded {self.timeout}s timeout")
249
+
250
+ # Set timeout alarm (Unix only)
251
+ try:
252
+ signal.signal(signal.SIGALRM, timeout_handler)
253
+ signal.alarm(self.timeout)
254
+ result = self._executor.execute(self.task)
255
+ signal.alarm(0) # Cancel alarm
256
+ except AttributeError:
257
+ # Windows doesn't support SIGALRM, use threading.Timer fallback
258
+ logger.warning("Timeout not supported on this platform, executing without timeout")
259
+ result = self._executor.execute(self.task)
260
+ else:
261
+ result = self._executor.execute(self.task)
262
+
263
+ logger.debug(f"Agent execution successful on attempt {attempt + 1}")
264
+ logger.debug(f"Result: {result}")
265
+
266
+ # Always print result to stdout (even in non-verbose mode)
267
+ print(f"\n✅ Agent Response:\n{result}\n")
268
+
269
+ # Estimate cost (rough: ~$0.0001 per execution for gpt-4o-mini)
270
+ estimated_cost = 0.0001 # Base cost estimate
271
+ self._total_cost += estimated_cost
272
+ logger.debug(f"Estimated cost this run: ${estimated_cost:.4f}, Total: ${self._total_cost:.4f}")
273
+
274
+ self._success_count += 1
275
+ success = True
276
+
277
+ if self.on_success:
278
+ try:
279
+ self.on_success(result)
280
+ except Exception as e:
281
+ logger.error(f"Callback error in on_success: {e}")
282
+
283
+ # Update state file after successful execution
284
+ self._update_state_if_daemon()
285
+
286
+ break
287
+
288
+ except TimeoutError as e:
289
+ logger.error(f"Execution timeout on attempt {attempt + 1}: {e}")
290
+
291
+ except Exception as e:
292
+ logger.error(f"Agent execution failed on attempt {attempt + 1}: {e}")
293
+
294
+ if attempt < max_retries - 1:
295
+ wait_time = 30 * (attempt + 1) # Exponential backoff
296
+ logger.debug(f"Waiting {wait_time}s before retry...")
297
+ time.sleep(wait_time)
298
+
299
+ if not success:
300
+ self._failure_count += 1
301
+ logger.error(f"Agent execution failed after {max_retries} attempts")
302
+
303
+ if self.on_failure:
304
+ try:
305
+ self.on_failure(f"Failed after {max_retries} attempts")
306
+ except Exception as e:
307
+ logger.error(f"Callback error in on_failure: {e}")
308
+
309
+ # Update state file even after failure
310
+ self._update_state_if_daemon()
311
+
312
+ def execute_once(self) -> Any:
313
+ """
314
+ Execute agent immediately (one-time execution).
315
+
316
+ Returns:
317
+ Agent execution result
318
+ """
319
+ logger.debug("Executing agent once")
320
+ try:
321
+ result = self._executor.execute(self.task)
322
+ logger.debug(f"One-time execution successful: {result}")
323
+
324
+ if self.on_success:
325
+ try:
326
+ self.on_success(result)
327
+ except Exception as e:
328
+ logger.error(f"Callback error in on_success: {e}")
329
+
330
+ return result
331
+ except Exception as e:
332
+ logger.error(f"One-time execution failed: {e}")
333
+ raise
334
+
335
+ @classmethod
336
+ def from_yaml(
337
+ cls,
338
+ yaml_path: str = "agents.yaml",
339
+ interval_override: Optional[str] = None,
340
+ max_retries_override: Optional[int] = None,
341
+ timeout_override: Optional[int] = None,
342
+ max_cost_override: Optional[float] = None,
343
+ on_success: Optional[Callable] = None,
344
+ on_failure: Optional[Callable] = None
345
+ ) -> 'AgentScheduler':
346
+ """
347
+ Create AgentScheduler from agents.yaml file.
348
+
349
+ Args:
350
+ yaml_path: Path to agents.yaml file
351
+ interval_override: Override schedule interval from YAML
352
+ max_retries_override: Override max_retries from YAML
353
+ on_success: Callback function on successful execution
354
+ on_failure: Callback function on failed execution
355
+
356
+ Returns:
357
+ Configured AgentScheduler instance
358
+
359
+ Example:
360
+ scheduler = AgentScheduler.from_yaml("agents.yaml")
361
+ scheduler.start()
362
+
363
+ Example agents.yaml:
364
+ framework: praisonai
365
+
366
+ agents:
367
+ - name: "AI News Monitor"
368
+ role: "News Analyst"
369
+ instructions: "Search and summarize AI news"
370
+ tools:
371
+ - search_tool
372
+
373
+ task: "Search for latest AI news"
374
+
375
+ schedule:
376
+ interval: "hourly"
377
+ max_retries: 3
378
+ run_immediately: true
379
+ """
380
+ from .yaml_loader import load_agent_yaml_with_schedule, create_agent_from_config
381
+
382
+ # Load configuration from YAML
383
+ agent_config, schedule_config = load_agent_yaml_with_schedule(yaml_path)
384
+
385
+ # Create agent from config
386
+ agent = create_agent_from_config(agent_config)
387
+
388
+ # Get task
389
+ task = agent_config.get('task', '')
390
+ if not task:
391
+ raise ValueError("No task specified in YAML file")
392
+
393
+ # Apply overrides to schedule config
394
+ if interval_override:
395
+ schedule_config['interval'] = interval_override
396
+ if max_retries_override is not None:
397
+ schedule_config['max_retries'] = max_retries_override
398
+ if timeout_override is not None:
399
+ schedule_config['timeout'] = timeout_override
400
+ if max_cost_override is not None:
401
+ schedule_config['max_cost'] = max_cost_override
402
+
403
+ # Create scheduler instance with timeout and cost limits
404
+ scheduler = cls(
405
+ agent=agent,
406
+ task=task,
407
+ config=agent_config,
408
+ timeout=schedule_config.get('timeout'),
409
+ max_cost=schedule_config.get('max_cost'),
410
+ on_success=on_success,
411
+ on_failure=on_failure
412
+ )
413
+
414
+ # Store schedule config for later use
415
+ scheduler._yaml_schedule_config = schedule_config
416
+
417
+ return scheduler
418
+
419
+ def start_from_yaml_config(self) -> bool:
420
+ """
421
+ Start scheduler using configuration from YAML file.
422
+
423
+ Must be called after from_yaml() class method.
424
+
425
+ Returns:
426
+ True if started successfully
427
+ """
428
+ if not hasattr(self, '_yaml_schedule_config'):
429
+ raise ValueError("No YAML configuration found. Use from_yaml() first.")
430
+
431
+ schedule_config = self._yaml_schedule_config
432
+ interval = schedule_config.get('interval', 'hourly')
433
+ max_retries = schedule_config.get('max_retries', 3)
434
+ run_immediately = schedule_config.get('run_immediately', False)
435
+
436
+ return self.start(interval, max_retries, run_immediately)
437
+
438
+
439
+ @classmethod
440
+ def from_recipe(
441
+ cls,
442
+ recipe_name: str,
443
+ *,
444
+ input_data: Any = None,
445
+ config: Optional[Dict[str, Any]] = None,
446
+ interval_override: Optional[str] = None,
447
+ max_retries_override: Optional[int] = None,
448
+ timeout_override: Optional[int] = None,
449
+ max_cost_override: Optional[float] = None,
450
+ on_success: Optional[Callable] = None,
451
+ on_failure: Optional[Callable] = None
452
+ ) -> 'AgentScheduler':
453
+ """
454
+ Create AgentScheduler from a recipe name.
455
+
456
+ Args:
457
+ recipe_name: Name of the recipe to schedule
458
+ input_data: Input data for the recipe
459
+ config: Configuration overrides for the recipe
460
+ interval_override: Override schedule interval from recipe runtime config
461
+ max_retries_override: Override max_retries from recipe runtime config
462
+ timeout_override: Override timeout from recipe runtime config
463
+ max_cost_override: Override max_cost from recipe runtime config
464
+ on_success: Callback function on successful execution
465
+ on_failure: Callback function on failed execution
466
+
467
+ Returns:
468
+ Configured AgentScheduler instance
469
+
470
+ Example:
471
+ scheduler = AgentScheduler.from_recipe("news-monitor")
472
+ scheduler.start(schedule_expr="hourly")
473
+ """
474
+ from praisonai.recipe.bridge import resolve, execute_resolved_recipe, get_recipe_task_description
475
+
476
+ # Resolve the recipe
477
+ resolved = resolve(
478
+ recipe_name,
479
+ input_data=input_data,
480
+ config=config or {},
481
+ options={'timeout_sec': timeout_override or 300},
482
+ )
483
+
484
+ # Get runtime config defaults from recipe
485
+ interval = interval_override or "hourly"
486
+ max_retries = max_retries_override if max_retries_override is not None else 3
487
+ timeout = timeout_override or 300
488
+ max_cost = max_cost_override if max_cost_override is not None else 1.00
489
+
490
+ runtime = resolved.runtime_config
491
+ if runtime and hasattr(runtime, 'schedule'):
492
+ sched_config = runtime.schedule
493
+ interval = interval_override or sched_config.interval
494
+ max_retries = max_retries_override if max_retries_override is not None else sched_config.max_retries
495
+ timeout = timeout_override or sched_config.timeout_sec
496
+ max_cost = max_cost_override if max_cost_override is not None else sched_config.max_cost_usd
497
+
498
+ # Create a recipe executor agent wrapper
499
+ class RecipeExecutorAgent:
500
+ """Wrapper that makes a recipe look like an agent for the scheduler."""
501
+ def __init__(self, resolved_recipe):
502
+ self.resolved = resolved_recipe
503
+ self.name = f"RecipeAgent:{resolved_recipe.name}"
504
+
505
+ def start(self, task: str) -> Any:
506
+ return execute_resolved_recipe(self.resolved)
507
+
508
+ # Create the agent wrapper
509
+ agent = RecipeExecutorAgent(resolved)
510
+ task = get_recipe_task_description(resolved)
511
+
512
+ # Create scheduler instance
513
+ scheduler = cls(
514
+ agent=agent,
515
+ task=task,
516
+ timeout=timeout,
517
+ max_cost=max_cost,
518
+ on_success=on_success,
519
+ on_failure=on_failure,
520
+ )
521
+
522
+ # Store recipe metadata and schedule config
523
+ scheduler._recipe_name = recipe_name
524
+ scheduler._recipe_resolved = resolved
525
+ scheduler._yaml_schedule_config = {
526
+ 'interval': interval,
527
+ 'max_retries': max_retries,
528
+ 'run_immediately': False,
529
+ 'timeout': timeout,
530
+ 'max_cost': max_cost,
531
+ }
532
+
533
+ return scheduler
534
+
535
+
536
+ def create_agent_scheduler(
537
+ agent,
538
+ task: str,
539
+ config: Optional[Dict[str, Any]] = None
540
+ ) -> AgentScheduler:
541
+ """
542
+ Factory function to create agent scheduler.
543
+
544
+ Args:
545
+ agent: PraisonAI Agent instance
546
+ task: Task description
547
+ config: Optional configuration
548
+
549
+ Returns:
550
+ Configured AgentScheduler instance
551
+ """
552
+ return AgentScheduler(agent, task, config)
@@ -0,0 +1,124 @@
1
+ """
2
+ Base components for PraisonAI Scheduler.
3
+
4
+ This module provides shared functionality for all schedulers:
5
+ - ScheduleParser: Parse schedule expressions into intervals
6
+ - ExecutorInterface: Abstract interface for executors
7
+ - PraisonAgentExecutor: Executor for PraisonAI agents
8
+ """
9
+
10
+ import logging
11
+ from abc import ABC, abstractmethod
12
+ from typing import Any
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ScheduleParser:
18
+ """Parse schedule expressions into intervals in seconds."""
19
+
20
+ @staticmethod
21
+ def parse(schedule_expr: str) -> int:
22
+ """
23
+ Parse schedule expression and return interval in seconds.
24
+
25
+ Supported formats:
26
+ - "daily" -> 86400 seconds
27
+ - "hourly" -> 3600 seconds
28
+ - "*/30m" -> 1800 seconds (every 30 minutes)
29
+ - "*/6h" -> 21600 seconds (every 6 hours)
30
+ - "*/30s" -> 30 seconds (every 30 seconds)
31
+ - "3600" -> 3600 seconds (plain number)
32
+
33
+ Args:
34
+ schedule_expr: Schedule expression string
35
+
36
+ Returns:
37
+ Interval in seconds
38
+
39
+ Raises:
40
+ ValueError: If schedule format is not supported
41
+
42
+ Examples:
43
+ >>> ScheduleParser.parse("hourly")
44
+ 3600
45
+ >>> ScheduleParser.parse("*/30m")
46
+ 1800
47
+ >>> ScheduleParser.parse("daily")
48
+ 86400
49
+ """
50
+ schedule_expr = schedule_expr.strip().lower()
51
+
52
+ if schedule_expr == "daily":
53
+ return 86400
54
+ elif schedule_expr == "hourly":
55
+ return 3600
56
+ elif schedule_expr.isdigit():
57
+ return int(schedule_expr)
58
+ elif schedule_expr.startswith("*/"):
59
+ interval_part = schedule_expr[2:]
60
+ if interval_part.endswith("m"):
61
+ minutes = int(interval_part[:-1])
62
+ return minutes * 60
63
+ elif interval_part.endswith("h"):
64
+ hours = int(interval_part[:-1])
65
+ return hours * 3600
66
+ elif interval_part.endswith("s"):
67
+ return int(interval_part[:-1])
68
+ else:
69
+ return int(interval_part)
70
+ else:
71
+ raise ValueError(f"Unsupported schedule format: {schedule_expr}")
72
+
73
+
74
+ class ExecutorInterface(ABC):
75
+ """Abstract interface for executors."""
76
+
77
+ @abstractmethod
78
+ def execute(self, task: str) -> Any:
79
+ """
80
+ Execute a task.
81
+
82
+ Args:
83
+ task: Task description or instruction
84
+
85
+ Returns:
86
+ Execution result
87
+
88
+ Raises:
89
+ Exception: If execution fails
90
+ """
91
+ pass
92
+
93
+
94
+ class PraisonAgentExecutor(ExecutorInterface):
95
+ """Executor for PraisonAI agents."""
96
+
97
+ def __init__(self, agent):
98
+ """
99
+ Initialize executor with a PraisonAI agent.
100
+
101
+ Args:
102
+ agent: PraisonAI Agent instance (must have start() method)
103
+ """
104
+ self.agent = agent
105
+
106
+ def execute(self, task: str) -> Any:
107
+ """
108
+ Execute the agent with given task.
109
+
110
+ Args:
111
+ task: Task description for the agent
112
+
113
+ Returns:
114
+ Agent execution result
115
+
116
+ Raises:
117
+ Exception: If agent execution fails
118
+ """
119
+ try:
120
+ result = self.agent.start(task)
121
+ return result
122
+ except Exception as e:
123
+ logger.error(f"Agent execution failed: {e}")
124
+ raise