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
@@ -0,0 +1,408 @@
1
+
2
+ """
3
+ Centralized state management for Task agents.
4
+
5
+ This module consolidates agent state that was previously scattered across
6
+ multiple attributes in the Agent class.
7
+
8
+ OPTIMIZATION (Nov 14, 2025): Added 199 IQ loop detection fields
9
+ """
10
+
11
+ import time
12
+ import json
13
+ import os
14
+ import tempfile
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ from typing import Optional, Any, List
19
+
20
+
21
+ @dataclass
22
+ class AgentState:
23
+ """Centralized agent execution state with intelligent loop detection.
24
+
25
+ All agent state should be managed through this class to ensure
26
+ consistency and prevent scattered attributes.
27
+
28
+ OPTIMIZATION (Nov 14, 2025): Added loop detection capabilities
29
+ - Action similarity tracking
30
+ - Consecutive similar action counter
31
+ - Finding frequency monitoring
32
+ - Stall detection
33
+ """
34
+
35
+ # Step tracking
36
+ n_steps: int = 1
37
+ max_steps: Optional[int] = None
38
+ total_actions_count: int = 0
39
+
40
+ # Execution control flags
41
+ paused: bool = False
42
+ stopped: bool = False
43
+ done: bool = False
44
+
45
+ # Error tracking
46
+ consecutive_failures: int = 0
47
+ total_failures: int = 0
48
+ max_failures: int = 3
49
+
50
+ # History and results
51
+ last_result: Optional[List[Any]] = None
52
+ last_action: Optional[str] = None
53
+ last_model_output: Optional[Any] = None
54
+
55
+ # Timing
56
+ session_created_at: Optional[datetime] = None
57
+ last_activity_time: float = field(default_factory=time.time)
58
+ last_step_start_time: float = field(default_factory=time.time)
59
+
60
+ # Session metadata
61
+ session_continuation_context: Optional[str] = None
62
+
63
+ # OPTIMIZATION: Loop detection fields (Nov 14, 2025)
64
+ recent_action_texts: List[str] = field(default_factory=list) # Last 10 action summaries
65
+ consecutive_similar_actions: int = 0 # Counter for similar actions
66
+ last_finding_step: int = 0 # Step number when last finding was added
67
+ findings_last_n_steps: int = 0 # Count of findings in last N steps
68
+ loop_warning_count: int = 0 # How many loop warnings issued
69
+ max_loop_warnings: int = 3 # Trigger intervention after N warnings
70
+
71
+ # LLM Error Tracking (Dec 2025) - Circuit breaker pattern for provider failures
72
+ llm_error_type_counts: dict = field(default_factory=dict) # Error type → count
73
+ llm_providers_failed: List[str] = field(default_factory=list) # Providers that have failed
74
+ last_llm_error_type: Optional[str] = None # Most recent error type
75
+ llm_circuit_breaker_open: bool = False # True if circuit breaker tripped
76
+ CIRCUIT_BREAKER_THRESHOLD: int = 3 # Same error type N times → circuit breaker
77
+
78
+ def reset_failures(self) -> None:
79
+ """Reset consecutive failure count (called after successful step)."""
80
+ self.consecutive_failures = 0
81
+
82
+ def increment_failures(self) -> None:
83
+ """Increment failure counts."""
84
+ self.consecutive_failures += 1
85
+ self.total_failures += 1
86
+
87
+ def is_too_many_failures(self) -> bool:
88
+ """Check if consecutive failures exceed maximum."""
89
+ return self.consecutive_failures >= self.max_failures
90
+
91
+ def increment_step(self) -> None:
92
+ """Increment step counter."""
93
+ self.n_steps += 1
94
+
95
+ def increment_actions(self, count: int = 1) -> None:
96
+ """Increment total actions count."""
97
+ self.total_actions_count += count
98
+
99
+ def update_activity_time(self) -> None:
100
+ """Update last activity timestamp."""
101
+ self.last_activity_time = time.time()
102
+
103
+ def should_stop(self) -> bool:
104
+ """Check if agent should stop execution."""
105
+ return self.stopped or self.done
106
+
107
+ # OPTIMIZATION: Loop detection methods (Nov 14, 2025)
108
+
109
+ def track_action(self, action_summary: str) -> None:
110
+ """Track action for loop detection.
111
+
112
+ Args:
113
+ action_summary: Summary of the action taken
114
+ """
115
+ # Keep last 10 actions
116
+ self.recent_action_texts.append(action_summary)
117
+ if len(self.recent_action_texts) > 10:
118
+ self.recent_action_texts.pop(0)
119
+
120
+ # Check similarity with previous action
121
+ if len(self.recent_action_texts) >= 2:
122
+ current = action_summary.lower()
123
+ previous = self.recent_action_texts[-2].lower()
124
+
125
+ # Simple similarity: check for common words
126
+ current_words = set(current.split())
127
+ previous_words = set(previous.split())
128
+
129
+ if len(current_words) > 0 and len(previous_words) > 0:
130
+ overlap = len(current_words & previous_words)
131
+ similarity = overlap / max(len(current_words), len(previous_words))
132
+
133
+ if similarity > 0.7: # 70% word overlap = similar action
134
+ self.consecutive_similar_actions += 1
135
+ else:
136
+ self.consecutive_similar_actions = 0
137
+
138
+ def track_finding(self) -> None:
139
+ """Track that a finding was added (resets stall detection)."""
140
+ self.last_finding_step = self.n_steps
141
+ self.findings_last_n_steps += 1
142
+ self.consecutive_similar_actions = 0 # Reset on progress
143
+
144
+ def is_stuck_in_loop(self) -> bool:
145
+ """Check if agent is stuck in infinite loop.
146
+
147
+ Multi-signal detection:
148
+ 1. Same action repeated 5+ times
149
+ 2. No findings in 15+ steps
150
+ 3. Multiple loop warnings already issued
151
+
152
+ Returns:
153
+ True if loop detected, False otherwise
154
+ """
155
+ # Signal 1: Repetitive actions
156
+ if self.consecutive_similar_actions >= 5:
157
+ return True
158
+
159
+ # Signal 2: No progress (findings)
160
+ steps_since_finding = self.n_steps - self.last_finding_step
161
+ if steps_since_finding > 15:
162
+ return True
163
+
164
+ # Signal 3: Too many warnings (agent not responding to guidance)
165
+ if self.loop_warning_count >= self.max_loop_warnings:
166
+ return True
167
+
168
+ return False
169
+
170
+ def is_showing_loop_symptoms(self) -> bool:
171
+ """Check for early loop symptoms (warning signs).
172
+
173
+ Returns:
174
+ True if showing symptoms, False otherwise
175
+ """
176
+ # Symptom 1: Moderate repetition
177
+ if self.consecutive_similar_actions >= 3:
178
+ return True
179
+
180
+ # Symptom 2: Slow progress
181
+ steps_since_finding = self.n_steps - self.last_finding_step
182
+ if steps_since_finding > 8:
183
+ return True
184
+
185
+ return False
186
+
187
+ def reset_loop_detection(self) -> None:
188
+ """Reset loop detection counters (after successful intervention)."""
189
+ self.consecutive_similar_actions = 0
190
+ self.loop_warning_count = 0
191
+
192
+ # LLM Error Tracking methods (Dec 2025)
193
+
194
+ def track_llm_error(self, error_type: str, provider: str) -> bool:
195
+ """Track an LLM error for circuit breaker pattern.
196
+
197
+ Args:
198
+ error_type: Type of error (e.g., 'LLMRateLimitError')
199
+ provider: Provider that failed
200
+
201
+ Returns:
202
+ True if circuit breaker should trip (same error 3+ times)
203
+ """
204
+ # Track error type count
205
+ self.llm_error_type_counts[error_type] = self.llm_error_type_counts.get(error_type, 0) + 1
206
+ self.last_llm_error_type = error_type
207
+
208
+ # Track failed provider
209
+ if provider and provider not in self.llm_providers_failed:
210
+ self.llm_providers_failed.append(provider)
211
+
212
+ # Check circuit breaker threshold
213
+ if self.llm_error_type_counts[error_type] >= self.CIRCUIT_BREAKER_THRESHOLD:
214
+ self.llm_circuit_breaker_open = True
215
+ return True
216
+
217
+ return False
218
+
219
+ def reset_llm_errors(self, reset_circuit_breaker: bool = False) -> None:
220
+ """Reset LLM error tracking (after successful fallback).
221
+
222
+ Args:
223
+ reset_circuit_breaker: Also reset circuit breaker state
224
+ """
225
+ self.llm_error_type_counts.clear()
226
+ self.last_llm_error_type = None
227
+ if reset_circuit_breaker:
228
+ self.llm_circuit_breaker_open = False
229
+ self.llm_providers_failed.clear()
230
+
231
+ def should_halt_for_llm_error(self) -> bool:
232
+ """Check if session should halt due to LLM errors.
233
+
234
+ Returns:
235
+ True if circuit breaker is open or too many provider failures
236
+ """
237
+ return self.llm_circuit_breaker_open or len(self.llm_providers_failed) >= 3
238
+
239
+ def is_at_max_steps(self) -> bool:
240
+ """Check if maximum steps reached."""
241
+ if self.max_steps is None:
242
+ return False
243
+ return self.n_steps >= self.max_steps
244
+
245
+ # PERSISTENCE: Session state save/load for app restart recovery
246
+
247
+ def to_dict(self) -> dict:
248
+ """Serialize agent state to dictionary for persistence.
249
+
250
+ Returns:
251
+ Dictionary representation of agent state
252
+ """
253
+ return {
254
+ 'version': '1.1', # Updated for LLM error tracking
255
+ 'n_steps': self.n_steps,
256
+ 'max_steps': self.max_steps,
257
+ 'total_actions_count': self.total_actions_count,
258
+ 'paused': self.paused,
259
+ 'stopped': self.stopped,
260
+ 'done': self.done,
261
+ 'consecutive_failures': self.consecutive_failures,
262
+ 'total_failures': self.total_failures,
263
+ 'max_failures': self.max_failures,
264
+ 'last_action': self.last_action,
265
+ 'session_continuation_context': self.session_continuation_context,
266
+ # Loop detection state
267
+ 'recent_action_texts': self.recent_action_texts,
268
+ 'consecutive_similar_actions': self.consecutive_similar_actions,
269
+ 'last_finding_step': self.last_finding_step,
270
+ 'findings_last_n_steps': self.findings_last_n_steps,
271
+ 'loop_warning_count': self.loop_warning_count,
272
+ 'max_loop_warnings': self.max_loop_warnings,
273
+ # LLM error tracking (Dec 2025)
274
+ 'llm_error_type_counts': self.llm_error_type_counts,
275
+ 'llm_providers_failed': self.llm_providers_failed,
276
+ 'last_llm_error_type': self.last_llm_error_type,
277
+ 'llm_circuit_breaker_open': self.llm_circuit_breaker_open,
278
+ # Timestamps
279
+ 'session_created_at': self.session_created_at.isoformat() if self.session_created_at else None,
280
+ 'last_activity_time': self.last_activity_time,
281
+ 'last_step_start_time': self.last_step_start_time
282
+ }
283
+
284
+ @classmethod
285
+ def from_dict(cls, data: dict) -> 'AgentState':
286
+ """Deserialize agent state from dictionary.
287
+
288
+ Args:
289
+ data: Dictionary representation of agent state
290
+
291
+ Returns:
292
+ Restored AgentState instance
293
+
294
+ Raises:
295
+ ValueError: If version mismatch or invalid data
296
+ """
297
+ # Validate version (support 1.0 and 1.1)
298
+ version = data.get('version')
299
+ if version not in ('1.0', '1.1'):
300
+ raise ValueError(f"Unsupported agent state version: {version}")
301
+
302
+ # Create instance with restored data
303
+ state = cls(
304
+ n_steps=data.get('n_steps', 1),
305
+ max_steps=data.get('max_steps'),
306
+ total_actions_count=data.get('total_actions_count', 0),
307
+ paused=data.get('paused', False),
308
+ stopped=data.get('stopped', False),
309
+ done=data.get('done', False),
310
+ consecutive_failures=data.get('consecutive_failures', 0),
311
+ total_failures=data.get('total_failures', 0),
312
+ max_failures=data.get('max_failures', 3),
313
+ last_action=data.get('last_action'),
314
+ session_continuation_context=data.get('session_continuation_context'),
315
+ # Loop detection
316
+ recent_action_texts=data.get('recent_action_texts', []),
317
+ consecutive_similar_actions=data.get('consecutive_similar_actions', 0),
318
+ last_finding_step=data.get('last_finding_step', 0),
319
+ findings_last_n_steps=data.get('findings_last_n_steps', 0),
320
+ loop_warning_count=data.get('loop_warning_count', 0),
321
+ max_loop_warnings=data.get('max_loop_warnings', 3),
322
+ # LLM error tracking (Dec 2025) - defaults for v1.0 compatibility
323
+ llm_error_type_counts=data.get('llm_error_type_counts', {}),
324
+ llm_providers_failed=data.get('llm_providers_failed', []),
325
+ last_llm_error_type=data.get('last_llm_error_type'),
326
+ llm_circuit_breaker_open=data.get('llm_circuit_breaker_open', False),
327
+ # Timestamps
328
+ session_created_at=datetime.fromisoformat(data['session_created_at']) if data.get('session_created_at') else None,
329
+ last_activity_time=data.get('last_activity_time', time.time()),
330
+ last_step_start_time=data.get('last_step_start_time', time.time())
331
+ )
332
+ return state
333
+
334
+ def save_to_file(self, filepath: Path) -> bool:
335
+ """Save agent state to JSON file with atomic write.
336
+
337
+ Args:
338
+ filepath: Path to save state file
339
+
340
+ Returns:
341
+ True if save successful, False otherwise
342
+ """
343
+ try:
344
+ filepath = Path(filepath)
345
+ filepath.parent.mkdir(parents=True, exist_ok=True)
346
+
347
+ # Atomic write using temp file + rename
348
+ temp_fd, temp_path = tempfile.mkstemp(
349
+ dir=filepath.parent,
350
+ prefix='.agent_state_',
351
+ suffix='.tmp'
352
+ )
353
+
354
+ try:
355
+ with os.fdopen(temp_fd, 'w') as f:
356
+ json.dump(self.to_dict(), f, indent=2)
357
+ f.flush()
358
+ os.fsync(f.fileno())
359
+
360
+ # Atomic rename
361
+ os.replace(temp_path, str(filepath))
362
+ return True
363
+ except Exception:
364
+ # Clean up temp file on error
365
+ if os.path.exists(temp_path):
366
+ os.unlink(temp_path)
367
+ raise
368
+
369
+ except Exception as e:
370
+ # Log error but don't crash agent
371
+ import logging
372
+ logging.getLogger(__name__).error(f"Failed to save agent state: {e}")
373
+ return False
374
+
375
+ @classmethod
376
+ def load_from_file(cls, filepath: Path) -> Optional['AgentState']:
377
+ """Load agent state from JSON file.
378
+
379
+ Args:
380
+ filepath: Path to state file
381
+
382
+ Returns:
383
+ Restored AgentState instance, or None if file doesn't exist or invalid
384
+ """
385
+ try:
386
+ filepath = Path(filepath)
387
+ if not filepath.exists():
388
+ return None
389
+
390
+ with open(filepath, 'r') as f:
391
+ data = json.load(f)
392
+
393
+ return cls.from_dict(data)
394
+ except Exception as e:
395
+ # Log error but don't crash agent
396
+ import logging
397
+ logging.getLogger(__name__).error(f"Failed to load agent state: {e}")
398
+ return None
399
+
400
+ def __repr__(self) -> str:
401
+ """Return string representation for debugging."""
402
+ return (
403
+ f"AgentState("
404
+ f"steps={self.n_steps}/{self.max_steps or '∞'}, "
405
+ f"actions={self.total_actions_count}, "
406
+ f"failures={self.consecutive_failures}/{self.max_failures}, "
407
+ f"paused={self.paused}, stopped={self.stopped}, done={self.done})"
408
+ )
@@ -0,0 +1,274 @@
1
+ """Durable async delegation (UP-12) — `delegate_task(background=true)`.
2
+
3
+ POLYROB's children are `asyncio` coroutines on the agent's event loop, so the
4
+ async-equivalent of Reference' daemon thread pool is a manager-owned set of detached
5
+ `asyncio.Task`s. `AsyncDelegationRegistry.dispatch()` records the job under a single
6
+ lock (capacity check + insert is atomic — no TOCTOU where two dispatches both pass the
7
+ cap) and detaches `asyncio.create_task(self._run_and_deliver(...))`, returning a handle
8
+ immediately. The detached task runs `SubAgentManager.run_subtask(...)` (reusing its
9
+ concurrency semaphore + timeout + output extraction) and, on completion (success /
10
+ error / timeout), delivers exactly one self-contained completion block back into the
11
+ originating session via an injected `deliver` callback.
12
+
13
+ The completion re-enters the parent as a NEW turn through the existing HITL
14
+ user-message ingress (orchestrator.submit_user_message → hitl_manager queue →
15
+ run-loop drain → inject_user_guidance), so strict message-role alternation and the
16
+ prompt cache stay intact — we never splice a result between a tool result and an
17
+ assistant message.
18
+
19
+ Scope: durable ACROSS THE TURN (a background child outlives the dispatching turn and
20
+ re-enters a later turn of the same in-process session). NOT across process restart —
21
+ that is the scheduler's job (cron/). Single-worker: a completion for a session that
22
+ moved workers is parked/logged by submit_user_message, never forwarded (P2-8).
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import asyncio
27
+ import logging
28
+ import time
29
+ from dataclasses import dataclass, field
30
+ from typing import Any, Awaitable, Callable, Dict, List, Optional
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def _frame_delegation_payload(result_text: str) -> str:
36
+ """Frame an untrusted delegation result as DATA (P1-3).
37
+
38
+ Wraps the child output in ``<untrusted_tool_result>`` delimiters and defangs any
39
+ literal ``</delegation-result>`` in the payload so it cannot close the outer
40
+ envelope. Fail-open: framing is defense-in-depth, never a hard dependency.
41
+ """
42
+ text = result_text if isinstance(result_text, str) else str(result_text)
43
+ # Defang the envelope close tag (case-insensitive) so injected output can't break out.
44
+ import re
45
+ text = re.sub(r"</\s*delegation-result\s*>", "<!-- /delegation-result -->", text,
46
+ flags=re.IGNORECASE)
47
+ try:
48
+ from agents.task.agent.core.untrusted_wrap import wrap_untrusted
49
+ return wrap_untrusted("async_delegation", text)
50
+ except Exception:
51
+ return text # fail-open
52
+
53
+ # Keep at most this many finished records so the dict doesn't grow unbounded.
54
+ _MAX_RETAINED_COMPLETED = 50
55
+
56
+
57
+ @dataclass
58
+ class DelegationRecord:
59
+ delegation_id: str
60
+ goal: str
61
+ profile: str
62
+ parent_agent_id: Optional[str] = None
63
+ status: str = "running" # running | completed | error | timeout
64
+ dispatched_at: float = 0.0
65
+ completed_at: Optional[float] = None
66
+ task: Optional[asyncio.Task] = field(default=None, repr=False)
67
+
68
+
69
+ # A deliver callback: (DelegationRecord, result_text) -> awaitable. Injected so the
70
+ # registry is unit-testable without an orchestrator.
71
+ DeliverFn = Callable[[DelegationRecord, str], Awaitable[None]]
72
+
73
+
74
+ class AsyncDelegationRegistry:
75
+ """Owns detached background delegations + their lifecycle for one session."""
76
+
77
+ def __init__(self, manager: Any, deliver: DeliverFn, *,
78
+ clock: Callable[[], float] = time.time,
79
+ store: Any = None, session_id: str = "", user_id: str = ""):
80
+ self._manager = manager
81
+ self._deliver = deliver
82
+ self._clock = clock
83
+ self._lock = asyncio.Lock()
84
+ self._records: Dict[str, DelegationRecord] = {}
85
+ self._counter = 0
86
+ # Optional AutonomyStateStore write-through — restart-durable
87
+ # record of dispatched/terminal delegations (recovery in autonomy_state.py).
88
+ # Fail-open: any store error degrades to the legacy in-memory behavior.
89
+ self._store = store
90
+ self._session_id = session_id
91
+ self._user_id = user_id
92
+ # Counter seeding (past persisted ids) is LAZY — done under the dispatch
93
+ # lock on first use, so constructing an orchestrator costs zero sqlite I/O.
94
+ self._counter_seeded = store is None or not session_id
95
+
96
+ # -- introspection (for a future delegation_status tool; out of v1 scope) --
97
+ def active_count(self) -> int:
98
+ return sum(1 for r in self._records.values() if r.status == "running")
99
+
100
+ def list(self) -> List[DelegationRecord]:
101
+ return list(self._records.values())
102
+
103
+ def _next_id(self) -> str:
104
+ self._counter += 1
105
+ return f"deleg_{self._counter:04d}"
106
+
107
+ async def dispatch(
108
+ self,
109
+ *,
110
+ goal: str,
111
+ profile: str = "executor",
112
+ max_steps: int = 30,
113
+ parent_agent_id: str,
114
+ caller_is_sub: bool = False,
115
+ ) -> Dict[str, Any]:
116
+ """Capacity-check + record + detach. Returns a handle dict immediately.
117
+
118
+ At capacity (MAX_ASYNC_SUB_AGENTS background slots live) the dispatch is
119
+ REJECTED (not queued) so a runaway model can't pile up background work.
120
+ """
121
+ from agents.task.constants import TimeoutConfig
122
+
123
+ cap = TimeoutConfig.get_max_async_sub_agents()
124
+ async with self._lock:
125
+ if not self._counter_seeded:
126
+ try:
127
+ self._counter = max(self._counter,
128
+ self._store.max_counter(self._session_id))
129
+ except Exception:
130
+ logger.warning("delegation counter seed failed", exc_info=True)
131
+ self._counter_seeded = True
132
+ if self.active_count() >= cap:
133
+ return {
134
+ "status": "rejected",
135
+ "error": (
136
+ f"Background delegation capacity reached ({cap} live). Run this "
137
+ "synchronously (background=false) or wait for a slot to free."
138
+ ),
139
+ }
140
+ delegation_id = self._next_id()
141
+ rec = DelegationRecord(
142
+ delegation_id=delegation_id,
143
+ goal=goal,
144
+ profile=profile,
145
+ parent_agent_id=parent_agent_id,
146
+ dispatched_at=self._clock(),
147
+ )
148
+ self._records[delegation_id] = rec
149
+ if self._store is not None:
150
+ try:
151
+ self._store.record_dispatched(
152
+ session_id=self._session_id, user_id=self._user_id,
153
+ delegation_id=delegation_id, goal=goal, profile=profile,
154
+ parent_agent_id=parent_agent_id,
155
+ dispatched_at=rec.dispatched_at,
156
+ )
157
+ except Exception:
158
+ logger.warning("delegation durable write failed", exc_info=True)
159
+ rec.task = asyncio.create_task(
160
+ self._run_and_deliver(
161
+ rec, goal=goal, profile=profile, max_steps=max_steps,
162
+ parent_agent_id=parent_agent_id, caller_is_sub=caller_is_sub,
163
+ )
164
+ )
165
+ self._prune_completed()
166
+ return {"status": "dispatched", "delegation_id": delegation_id}
167
+
168
+ async def _run_and_deliver(
169
+ self,
170
+ rec: DelegationRecord,
171
+ *,
172
+ goal: str,
173
+ profile: str,
174
+ max_steps: int,
175
+ parent_agent_id: str,
176
+ caller_is_sub: bool,
177
+ ) -> None:
178
+ """Run the child to completion and deliver exactly one completion block."""
179
+ status = "completed"
180
+ result_text = ""
181
+ try:
182
+ result = await self._manager.run_subtask(
183
+ task=goal,
184
+ parent_agent_id=parent_agent_id,
185
+ profile_id=profile,
186
+ max_steps=max_steps,
187
+ is_parent_sub_agent=caller_is_sub,
188
+ )
189
+ if getattr(result, "success", False):
190
+ status = "completed"
191
+ result_text = self._extract_output(result)
192
+ else:
193
+ status = "error"
194
+ result_text = getattr(result, "error", None) or "Background task failed."
195
+ except asyncio.TimeoutError:
196
+ status = "timeout"
197
+ result_text = "Background task timed out."
198
+ except asyncio.CancelledError:
199
+ # Don't deliver on cancellation (shutdown); re-raise so the loop sees it.
200
+ # Set the LOCAL `status` (not rec.status) — the finally below writes
201
+ # rec.status = status, so assigning rec.status here would be clobbered
202
+ # back to "completed". After the re-raise, the delivery code below is
203
+ # skipped (finally runs, then the exception propagates).
204
+ status = "cancelled"
205
+ raise
206
+ except Exception as e: # noqa: BLE001 — fail-loud-log, never crash the loop
207
+ status = "error"
208
+ result_text = f"Background task raised: {e}"
209
+ logger.error("async delegation %s crashed: %s", rec.delegation_id, e, exc_info=True)
210
+ finally:
211
+ rec.status = status
212
+ rec.completed_at = self._clock()
213
+ if self._store is not None:
214
+ try:
215
+ self._store.record_terminal(
216
+ self._session_id, rec.delegation_id, status=status,
217
+ completed_at=rec.completed_at, result_text=result_text,
218
+ )
219
+ except Exception:
220
+ logger.warning("delegation terminal write failed", exc_info=True)
221
+
222
+ block = self._format_completion_block(rec, status, result_text)
223
+ try:
224
+ await self._deliver(rec, block)
225
+ except Exception as e: # noqa: BLE001 — delivery is best-effort (e.g. queue full)
226
+ logger.error(
227
+ "async delegation %s finished (%s) but delivery failed: %s",
228
+ rec.delegation_id, status, e,
229
+ )
230
+
231
+ @staticmethod
232
+ def _extract_output(result: Any) -> str:
233
+ # SubAgentResult.output_text property gives the string form; fall back to str().
234
+ for attr in ("output_text", "output"):
235
+ val = getattr(result, attr, None)
236
+ if isinstance(val, str) and val:
237
+ return val
238
+ if val is not None and not isinstance(val, str):
239
+ return str(val)
240
+ return "(no output)"
241
+
242
+ @staticmethod
243
+ def _format_completion_block(rec: DelegationRecord, status: str, result_text: str) -> str:
244
+ """Self-contained block so a parent deep in unrelated context knows why this
245
+ arrived (mirrors Reference' task-source block).
246
+
247
+ P1-3: the child's ``result_text`` is UNTRUSTED — it can quote injected web/MCP
248
+ content verbatim (the child's tool results were wrapped, but its final output
249
+ is not). Frame it as DATA (mirrors the self-wake rail's ``format_self_wake``):
250
+ the ``<delegation-result>`` envelope + trusted footer stay outside, the payload
251
+ goes inside an ``<untrusted_tool_result>`` block, and a literal
252
+ ``</delegation-result>`` in the payload is defanged so it can't break the
253
+ envelope.
254
+ """
255
+ emoji = {"completed": "✅", "error": "❌", "timeout": "⏱️"}.get(status, "ℹ️")
256
+ safe_result = _frame_delegation_payload(result_text)
257
+ return (
258
+ f"<delegation-result delegation_id=\"{rec.delegation_id}\" status=\"{status}\">\n"
259
+ f"{emoji} A background task you dispatched has finished.\n\n"
260
+ f"**delegation_id:** {rec.delegation_id}\n"
261
+ f"**goal:** {rec.goal}\n"
262
+ f"**status:** {status}\n\n"
263
+ f"**result:**\n{safe_result}\n\n"
264
+ "Use this result, or re-dispatch if the situation has changed.\n"
265
+ "</delegation-result>"
266
+ )
267
+
268
+ def _prune_completed(self) -> None:
269
+ done = [r for r in self._records.values() if r.status != "running"]
270
+ if len(done) <= _MAX_RETAINED_COMPLETED:
271
+ return
272
+ done.sort(key=lambda r: r.completed_at or 0.0)
273
+ for rec in done[: len(done) - _MAX_RETAINED_COMPLETED]:
274
+ self._records.pop(rec.delegation_id, None)