polyrob 0.5.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 (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
cron/jobs.py ADDED
@@ -0,0 +1,207 @@
1
+ """SQLite-backed cron job store (roadmap P5).
2
+
3
+ A single ``cron_jobs`` table holding durable schedule state. Uses WAL mode with a
4
+ jittered retry on write contention (mirrors Reference ``SessionDB`` and foreshadows
5
+ the P6 session-registry migration). The store is intentionally schedule-agnostic:
6
+ callers compute ``next_run_at`` with :mod:`cron.schedule` and hand it in.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import sqlite3
13
+ from dataclasses import dataclass, field
14
+ from datetime import datetime
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from core.sqlite_util import execute_retry
18
+
19
+
20
+ @dataclass
21
+ class CronJob:
22
+ id: str
23
+ task: str
24
+ schedule_spec: str
25
+ user_id: str
26
+ next_run_at: Optional[datetime]
27
+ one_shot: bool = False
28
+ # DORMANT (ME-D2): never consumed by the runner; retained to avoid a schema
29
+ # migration. Wire or drop in a dedicated proposal. Not settable via
30
+ # CronService.schedule — always takes this default.
31
+ skip_memory: bool = True
32
+ max_duration_seconds: int = 180
33
+ payload: Dict[str, Any] = field(default_factory=dict)
34
+ enabled: bool = True
35
+ status: str = "scheduled" # scheduled|running|done|failed|cancelled
36
+ last_run_at: Optional[datetime] = None
37
+ created_at: Optional[datetime] = None
38
+
39
+
40
+ def _iso(dt: Optional[datetime]) -> Optional[str]:
41
+ return dt.isoformat() if dt else None
42
+
43
+
44
+ def _parse(s: Optional[str]) -> Optional[datetime]:
45
+ return datetime.fromisoformat(s) if s else None
46
+
47
+
48
+ class CronJobStore:
49
+ def __init__(self, db_path: str):
50
+ self.db_path = db_path
51
+ parent = os.path.dirname(db_path)
52
+ if parent:
53
+ os.makedirs(parent, exist_ok=True)
54
+ self._init_schema()
55
+ # NOTE: crash-orphaned 'running' jobs are reclaimed by the scheduler at the
56
+ # start of each tick *under the held TickLock* (see CronScheduler._run_due),
57
+ # NOT here. Calling reclaim in __init__ ran without the lock, so a second
58
+ # CronJobStore built mid-tick (e.g. by the agent-facing CronJobTool, or
59
+ # another worker's lifespan under UVICORN_WORKERS>1) would reset a genuinely
60
+ # live job back to 'scheduled' and cause it to run twice.
61
+
62
+ def _init_schema(self) -> None:
63
+ execute_retry(
64
+ self.db_path,
65
+ """
66
+ CREATE TABLE IF NOT EXISTS cron_jobs (
67
+ id TEXT PRIMARY KEY,
68
+ task TEXT NOT NULL,
69
+ schedule_spec TEXT NOT NULL,
70
+ user_id TEXT NOT NULL,
71
+ next_run_at TEXT,
72
+ one_shot INTEGER NOT NULL DEFAULT 0,
73
+ -- DORMANT (ME-D2): never consumed by the runner; retained to avoid
74
+ -- a schema migration. Wire or drop in a dedicated proposal.
75
+ skip_memory INTEGER NOT NULL DEFAULT 1,
76
+ max_duration_seconds INTEGER NOT NULL DEFAULT 180,
77
+ payload TEXT NOT NULL DEFAULT '{}',
78
+ enabled INTEGER NOT NULL DEFAULT 1,
79
+ status TEXT NOT NULL DEFAULT 'scheduled',
80
+ last_run_at TEXT,
81
+ created_at TEXT NOT NULL
82
+ )
83
+ """
84
+ )
85
+
86
+ # --- row mapping ---------------------------------------------------------
87
+
88
+ @staticmethod
89
+ def _row_to_job(row: sqlite3.Row) -> CronJob:
90
+ return CronJob(
91
+ id=row["id"], task=row["task"], schedule_spec=row["schedule_spec"],
92
+ user_id=row["user_id"], next_run_at=_parse(row["next_run_at"]),
93
+ one_shot=bool(row["one_shot"]), skip_memory=bool(row["skip_memory"]),
94
+ max_duration_seconds=row["max_duration_seconds"],
95
+ payload=json.loads(row["payload"] or "{}"),
96
+ enabled=bool(row["enabled"]), status=row["status"],
97
+ last_run_at=_parse(row["last_run_at"]), created_at=_parse(row["created_at"]),
98
+ )
99
+
100
+ # --- CRUD ----------------------------------------------------------------
101
+
102
+ def add(self, job: CronJob) -> CronJob:
103
+ created = job.created_at or datetime(2026, 1, 1) # caller may override
104
+ execute_retry(
105
+ self.db_path,
106
+ """
107
+ INSERT INTO cron_jobs (id, task, schedule_spec, user_id, next_run_at,
108
+ one_shot, skip_memory, max_duration_seconds, payload, enabled, status,
109
+ last_run_at, created_at)
110
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
111
+ """,
112
+ (
113
+ job.id, job.task, job.schedule_spec, job.user_id, _iso(job.next_run_at),
114
+ int(job.one_shot), int(job.skip_memory), job.max_duration_seconds,
115
+ json.dumps(job.payload), int(job.enabled), job.status,
116
+ _iso(job.last_run_at), _iso(created),
117
+ ),
118
+ )
119
+ job.created_at = created
120
+ return job
121
+
122
+ def get(self, job_id: str, *, user_id: Optional[str] = None) -> Optional[CronJob]:
123
+ sql = "SELECT * FROM cron_jobs WHERE id=?"
124
+ params: tuple = (job_id,)
125
+ if user_id is not None:
126
+ sql += " AND user_id=?"
127
+ params = (job_id, user_id)
128
+ row = execute_retry(self.db_path, sql, params, fetch="one")
129
+ return self._row_to_job(row) if row else None
130
+
131
+ def list(self, user_id: Optional[str] = None, enabled_only: bool = False) -> List[CronJob]:
132
+ sql = "SELECT * FROM cron_jobs"
133
+ clauses, params = [], []
134
+ if user_id is not None:
135
+ clauses.append("user_id=?")
136
+ params.append(user_id)
137
+ if enabled_only:
138
+ clauses.append("enabled=1")
139
+ if clauses:
140
+ sql += " WHERE " + " AND ".join(clauses)
141
+ sql += " ORDER BY created_at"
142
+ rows = execute_retry(self.db_path, sql, tuple(params), fetch="all")
143
+ return [self._row_to_job(r) for r in rows]
144
+
145
+ def due(self, now: datetime) -> List[CronJob]:
146
+ rows = execute_retry(
147
+ self.db_path,
148
+ """
149
+ SELECT * FROM cron_jobs
150
+ WHERE enabled=1 AND status IN ('scheduled')
151
+ AND next_run_at IS NOT NULL AND next_run_at <= ?
152
+ ORDER BY next_run_at
153
+ """,
154
+ (_iso(now),), fetch="all",
155
+ )
156
+ return [self._row_to_job(r) for r in rows]
157
+
158
+ def update_after_run(self, job_id: str, *, last_run_at: datetime,
159
+ next_run_at: Optional[datetime], status: str) -> None:
160
+ execute_retry(
161
+ self.db_path,
162
+ "UPDATE cron_jobs SET last_run_at=?, next_run_at=?, status=? WHERE id=?",
163
+ (_iso(last_run_at), _iso(next_run_at), status, job_id),
164
+ )
165
+
166
+ def set_status(self, job_id: str, status: str) -> None:
167
+ execute_retry(self.db_path, "UPDATE cron_jobs SET status=? WHERE id=?", (status, job_id))
168
+
169
+ def claim_for_run(self, job_id: str) -> bool:
170
+ """Atomically transition a job 'scheduled' -> 'running'.
171
+
172
+ Returns True iff THIS caller won the claim (rowcount == 1). A compare-and-set
173
+ on status='scheduled' so two ticks can never both run the same job — the
174
+ non-CAS set_status('running') it replaces could double-run a job if a tick
175
+ ever raced a reclaim.
176
+ """
177
+ rowcount = execute_retry(
178
+ self.db_path,
179
+ "UPDATE cron_jobs SET status='running' WHERE id=? AND status='scheduled'",
180
+ (job_id,),
181
+ )
182
+ return bool(rowcount)
183
+
184
+ def reclaim_stale_running(self) -> int:
185
+ """Reset jobs stuck in 'running' (process died mid-run) back to 'scheduled'.
186
+
187
+ Returns the number of rows reclaimed. MUST be called only under the held cron
188
+ TickLock (the scheduler does this at the start of each tick). Ticks are
189
+ serialized by the lock and always write a terminal/rescheduled status after a
190
+ run, so any 'running' row observed under the lock is genuinely orphaned by a
191
+ crash and safe to reclaim. Calling it WITHOUT the lock (e.g. from __init__)
192
+ could reset a live job and cause a double-run.
193
+ """
194
+ return execute_retry(
195
+ self.db_path,
196
+ "UPDATE cron_jobs SET status='scheduled' WHERE status='running' AND enabled=1",
197
+ (),
198
+ ) or 0
199
+
200
+ def cancel(self, job_id: str, *, user_id: Optional[str] = None) -> bool:
201
+ sql = "UPDATE cron_jobs SET enabled=0, status='cancelled' WHERE id=?"
202
+ params: tuple = (job_id,)
203
+ if user_id is not None:
204
+ sql += " AND user_id=?"
205
+ params = (job_id, user_id)
206
+ rowcount = execute_retry(self.db_path, sql, params)
207
+ return bool(rowcount)
cron/runner.py ADDED
@@ -0,0 +1,297 @@
1
+ """Cron runner + ticker (roadmap P5).
2
+
3
+ Two pieces:
4
+
5
+ - :class:`CronTicker` — drives ``scheduler.tick()`` on an interval. Unit-tested
6
+ via an injected scheduler; the only background-loop concern is here.
7
+ - :func:`make_agent_runner` — the LIVE integration point that turns a ``CronJob``
8
+ into an agent session on the existing task-agent core, with the per-run cap. This
9
+ path needs a live agent run to verify (the scheduler's
10
+ unit tests use a fake runner), so it is opt-in via ``CRON_ENABLED`` and is not
11
+ wired into the default app lifecycle.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import logging
17
+ import os
18
+ import time
19
+ from datetime import datetime
20
+ from typing import Any, Awaitable, Callable, Optional
21
+
22
+
23
+ def default_cron_tools() -> list:
24
+ """Posture-aware default cron toolset (WS-8). Posture 0: ['filesystem','task']
25
+ (byte-identical). Posture>=1: + the compute tools so a scheduled build/self-env
26
+ run isn't tool-starved. Mirrors ``goals.dispatcher.default_goal_tools``; resolved
27
+ at call time so posture 0 is unchanged."""
28
+ try:
29
+ from agents.task.goals.dispatcher import default_goal_tools
30
+ return default_goal_tools()
31
+ except Exception:
32
+ return ["filesystem", "task"]
33
+
34
+
35
+ def _cron_ev(job, outcome: str, reason: Optional[str] = None, **extra) -> None:
36
+ """Emit a cron_run event to the durable event log (fail-open). Makes cron
37
+ lifecycle queryable in the uniform autonomy/governance stream, not just the
38
+ episodes table (telemetry audit 2026-07-04)."""
39
+ try:
40
+ from agents.task.telemetry.event_log import get_event_log, event_log_enabled
41
+ if event_log_enabled():
42
+ get_event_log().record(
43
+ "cron_run", user_id=getattr(job, "user_id", ""),
44
+ source="cron", job_id=getattr(job, "id", None),
45
+ outcome=outcome, reason=reason, **extra)
46
+ except Exception:
47
+ pass
48
+
49
+ from agents.task.runtime.run_as_session import run_task_as_session as _run_task_as_session
50
+ from cron.jobs import CronJob
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ def _cron_tick_is_active(result: Any) -> bool:
56
+ """A cron tick counts as 'active' (reset the idle-backoff interval to base)
57
+ when something actually ran or failed. A tick that was merely skipped (lock
58
+ contention, REPL busy) or found nothing due counts as idle. Named + module-level
59
+ so it's directly unit-testable without going through the async ticker loop.
60
+ """
61
+ return bool(getattr(result, "ran", None)) or bool(getattr(result, "failed", None))
62
+
63
+
64
+ class CronTicker:
65
+ """Periodically fire a scheduler tick until stopped."""
66
+
67
+ def __init__(self, scheduler: Any, interval_seconds: int = 60):
68
+ self.scheduler = scheduler
69
+ self.interval_seconds = interval_seconds
70
+
71
+ async def tick_once(self, now: Optional[datetime] = None):
72
+ return await self.scheduler.tick(now=now)
73
+
74
+ async def run_forever(self, stop_event: Optional[asyncio.Event] = None) -> None:
75
+ from core.tickers import IntervalTicker
76
+ from agents.task.constants import (
77
+ ticker_idle_backoff_enabled,
78
+ ticker_idle_backoff_max_multiplier,
79
+ )
80
+
81
+ is_active = None
82
+ max_interval = None
83
+ if ticker_idle_backoff_enabled():
84
+ is_active = _cron_tick_is_active
85
+ max_interval = self.interval_seconds * ticker_idle_backoff_max_multiplier()
86
+
87
+ await IntervalTicker(
88
+ self.scheduler.tick,
89
+ self.interval_seconds,
90
+ is_active=is_active,
91
+ max_interval_seconds=max_interval,
92
+ ).run_forever(stop_event=stop_event)
93
+
94
+
95
+ def make_agent_runner(task_agent: Any, *, data_dir: str = "data") -> Callable[[CronJob], Awaitable[bool]]:
96
+ """Build a runner that executes a CronJob as an isolated agent session.
97
+
98
+ LIVE PATH — reuses ``task_agent.create_session``. A cron run gets cross-session
99
+ memory recall like any other session; recall is tenant-scoped, so this is not
100
+ cross-USER contamination. Returns an async ``runner(job) -> bool`` suitable for
101
+ :class:`cron.scheduler.CronScheduler`. The scheduler already enforces
102
+ ``job.max_duration_seconds`` as a hard cap.
103
+ """
104
+ async def runner(job: CronJob) -> bool:
105
+ payload = dict(job.payload or {})
106
+ # Owner daily digest: a deterministic $0 tick composed from evidence and
107
+ # pushed via the delivery rail — never invokes the model. Routed here
108
+ # BEFORE the wake/change gates. Fail-open (a compose/deliver error is a
109
+ # $0 no-op, not a job failure) so a persistent hiccup can't spin.
110
+ if payload.get("digest"):
111
+ try:
112
+ from agents.task.constants import AutonomyConfig
113
+ if not AutonomyConfig.owner_digest_enabled():
114
+ _cron_ev(job, "skipped", "digest_disabled")
115
+ return True
116
+ from cron.digest import run_digest
117
+ ok = await run_digest(task_agent, job)
118
+ _cron_ev(job, "done" if ok else "skipped", "digest")
119
+ except Exception:
120
+ logger.warning("cron job %s: digest tick failed", job.id, exc_info=True)
121
+ return True
122
+ if not payload.get("wake_agent", True):
123
+ logger.info("cron job %s: wake_agent=False — $0 tick, agent not invoked", job.id)
124
+ _cron_ev(job, "skipped", "wake_agent_false")
125
+ return True
126
+ # Wake change-gate: a change-gated review tick whose state fingerprint is
127
+ # unchanged since the last SUCCESSFUL run is a $0 tick — same shape as
128
+ # wake_agent=False. Fail-open (any gate error runs the tick); delivery
129
+ # jobs are never gated. The outcome-tagged baseline is recorded AFTER the
130
+ # run (finally below): a failed run never establishes a skippable
131
+ # baseline, so a persistently-failing job always retries.
132
+ _gate_active = False
133
+ try:
134
+ from cron.wake_gate import gate_applies, should_skip_wake
135
+ _gate_active = gate_applies(job)
136
+ if _gate_active and should_skip_wake(job, data_dir=data_dir):
137
+ logger.info("cron job %s: no observable change — $0 tick, agent not invoked", job.id)
138
+ _cron_ev(job, "skipped", "no_change")
139
+ return True
140
+ except Exception:
141
+ logger.warning("cron job %s: wake gate error — running tick", job.id, exc_info=True)
142
+ ok = False
143
+ try:
144
+ ok = await _execute(job, payload)
145
+ return ok
146
+ finally:
147
+ if _gate_active:
148
+ try:
149
+ from cron.wake_gate import record_wake_outcome
150
+ record_wake_outcome(job, data_dir=data_dir, ok=bool(ok))
151
+ except Exception:
152
+ logger.warning("cron job %s: wake gate outcome record failed", job.id,
153
+ exc_info=True)
154
+
155
+ async def _execute(job: CronJob, payload: dict) -> bool:
156
+ session_id = None
157
+ _t0 = time.time()
158
+ _cron_ev(job, "started")
159
+ from core.runtime_config import resolve_runtime_config
160
+ provider = payload.get("provider") or resolve_runtime_config(None, None)[0]
161
+ # Fill the provider's default model from the registry when the job doesn't pin
162
+ # one — an autonomous run has no interactive config, and a None model crashes
163
+ # session setup downstream ('.lower()' on None). Mirrors the goal dispatcher.
164
+ model = payload.get("model")
165
+ if not model:
166
+ try:
167
+ from modules.llm.llm_client_registry import get_default_model
168
+ model = get_default_model(provider)
169
+ except Exception:
170
+ model = None
171
+ request = {
172
+ "task": job.task,
173
+ "provider": provider,
174
+ "model": model,
175
+ "tools": payload.get("tools", default_cron_tools()),
176
+ "max_steps": payload.get("max_steps", 20),
177
+ "temperature": 0.0,
178
+ "cron": True,
179
+ }
180
+ try:
181
+ from agents.task.constants import AutonomyConfig
182
+
183
+ # Legacy branch (CRON_RUN_LOOP=OFF): create-only, return bool(session_info).
184
+ # Kept exactly as-is — run_task_as_session does NOT apply here.
185
+ if not AutonomyConfig.cron_run_loop():
186
+ session_info = await task_agent.create_session(user_id=job.user_id, request=request)
187
+ return bool(session_info)
188
+
189
+ # W3 LIVE-BUG FIX: route through the shared helper so create_session AND
190
+ # run_session are both called and refusals are normalised to (sid, None).
191
+ session_id, final = await _run_task_as_session(
192
+ task_agent, user_id=job.user_id, request=request, autonomous=True
193
+ )
194
+
195
+ # Back-half: cron-specific log messages + early returns.
196
+ if session_id is None:
197
+ logger.error("cron job %s: create_session returned no id", job.id)
198
+ return False
199
+ if final is None:
200
+ # run_task_as_session already collapsed refusal/empty → None; we emit the
201
+ # original log so operators see one clear refusal line (no double-logging:
202
+ # run_task_as_session is intentionally silent on refusals).
203
+ logger.warning("cron job %s: run did not complete (refusal or empty)", job.id)
204
+ try:
205
+ from modules.memory.episodic import finalize_episode
206
+ await finalize_episode(
207
+ session_id=session_id, user_id=job.user_id, kind="cron",
208
+ task=job.task, outcome="failed",
209
+ meta={"source": "cron", "job_id": job.id},
210
+ )
211
+ except Exception:
212
+ logger.warning("cron episodic write failed", exc_info=True)
213
+ _cron_ev(job, "failed", "refusal_or_empty", duration_s=round(time.time() - _t0, 3))
214
+ return False
215
+
216
+ # Episodic write happens BEFORE out-of-band delivery (Task 7): delivery's
217
+ # surfaced-mark is a plain UPDATE keyed on session_id, so the row must
218
+ # already exist or the mark is a silent no-op. Order matters here.
219
+ try:
220
+ from modules.memory.episodic import finalize_episode, collect_provenance
221
+ orchestrator = task_agent.get_orchestrator(session_id)
222
+ prov = await collect_provenance(orchestrator)
223
+ await finalize_episode(
224
+ session_id=session_id, user_id=job.user_id, kind="cron",
225
+ task=job.task, outcome="done",
226
+ summary=str(final)[:2000] if final is not None else None,
227
+ spend_usd=prov["spend_usd"], steps=prov["steps"],
228
+ artifacts=prov["artifacts"],
229
+ meta={"source": "cron", "job_id": job.id},
230
+ )
231
+ _cron_ev(job, "done", duration_s=round(time.time() - _t0, 3),
232
+ spend_usd=prov.get("spend_usd"), steps=prov.get("steps"))
233
+ except Exception:
234
+ logger.warning("cron episodic write failed", exc_info=True)
235
+
236
+ # Out-of-band delivery (gated CRON_DELIVERY_ENABLED, default OFF). Runs
237
+ # inside the scheduler's wait_for budget; fail-open — never fails the job.
238
+ deliver = payload.get("deliver")
239
+ if AutonomyConfig.cron_delivery_enabled() and deliver and final:
240
+ try:
241
+ from cron.delivery import deliver_result, delivery_outcome
242
+ ok = await deliver_result(
243
+ task_agent, job, final,
244
+ target=deliver, deliver_target=payload.get("deliver_target"),
245
+ session_id=session_id,
246
+ )
247
+ # Observability: make proactive delivery verifiable in the journal.
248
+ # outcome distinguishes a [SILENT] opt-out (suppressed) from a real
249
+ # send failure (failed) — they used to both log as ok=False.
250
+ logger.info("cron job %s out-of-band delivery target=%s outcome=%s",
251
+ job.id, deliver, delivery_outcome(final, ok))
252
+ except Exception as e: # belt-and-suspenders; delivery is best-effort
253
+ logger.error("cron job %s delivery error: %s", job.id, e, exc_info=True)
254
+
255
+ return True
256
+ except Exception as e:
257
+ logger.error("cron job %s session failed: %s", job.id, e, exc_info=True)
258
+ _cron_ev(job, "failed", "exception", duration_s=round(time.time() - _t0, 3),
259
+ error=str(e)[:200])
260
+ if session_id:
261
+ try:
262
+ from modules.memory.episodic import finalize_episode
263
+ await finalize_episode(
264
+ session_id=session_id, user_id=job.user_id, kind="cron",
265
+ task=job.task, outcome="failed", summary=str(e)[:2000],
266
+ meta={"source": "cron", "job_id": job.id},
267
+ )
268
+ except Exception:
269
+ logger.warning("cron episodic write failed", exc_info=True)
270
+ return False
271
+
272
+ return runner
273
+
274
+
275
+ def build_cron_ticker(
276
+ task_agent: Any,
277
+ *,
278
+ data_dir: str = "data",
279
+ interval_seconds: int = 60,
280
+ ) -> CronTicker:
281
+ """Assemble the full cron stack into a :class:`CronTicker` (B-T1).
282
+
283
+ Shares the on-disk job store with the agent-facing ``cronjob`` tool — both use
284
+ ``<data_dir>/cron.db`` — so jobs scheduled by the tool are picked up by the
285
+ ticker. The tick lock (``<data_dir>/cron.tick.lock``) keeps a tick safe under
286
+ ``UVICORN_WORKERS>1``. Opt-in: the FastAPI lifespan only calls this when
287
+ ``CRON_ENABLED`` is set, so prod is unchanged by default.
288
+ """
289
+ from cron.jobs import CronJobStore
290
+ from cron.scheduler import CronScheduler
291
+
292
+ store = CronJobStore(os.path.join(data_dir, "cron.db"))
293
+ runner = make_agent_runner(task_agent, data_dir=data_dir)
294
+ scheduler = CronScheduler(
295
+ store, runner, lock_path=os.path.join(data_dir, "cron.tick.lock")
296
+ )
297
+ return CronTicker(scheduler, interval_seconds=interval_seconds)