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.
- agents/README.md +453 -0
- agents/__init__.py +164 -0
- agents/base_agent.py +743 -0
- agents/personality/character.py +119 -0
- agents/personality/character_manager.py +309 -0
- agents/personality/characters/rob.character.json +56 -0
- agents/personality/characters/trump.character.json +63 -0
- agents/personality/persona_render.py +78 -0
- agents/personality/persona_resolver.py +115 -0
- agents/prompt/__init__.py +58 -0
- agents/prompt/base_prompt.py +92 -0
- agents/prompt/system.py +417 -0
- agents/task/README_PATH_MANAGEMENT.md +141 -0
- agents/task/__init__.py +162 -0
- agents/task/agent/__init__.py +70 -0
- agents/task/agent/agent_state.py +408 -0
- agents/task/agent/async_delegation.py +274 -0
- agents/task/agent/autonomy_state.py +275 -0
- agents/task/agent/conversation.py +75 -0
- agents/task/agent/core/__init__.py +6 -0
- agents/task/agent/core/aux_metering.py +61 -0
- agents/task/agent/core/background_review.py +157 -0
- agents/task/agent/core/construction.py +1179 -0
- agents/task/agent/core/conversational_exit.py +49 -0
- agents/task/agent/core/correspondent_gate.py +227 -0
- agents/task/agent/core/curator.py +246 -0
- agents/task/agent/core/episodic_digest.py +103 -0
- agents/task/agent/core/error_recovery.py +480 -0
- agents/task/agent/core/history_io.py +257 -0
- agents/task/agent/core/llm_provisioning.py +287 -0
- agents/task/agent/core/llm_runner.py +547 -0
- agents/task/agent/core/logging_io.py +276 -0
- agents/task/agent/core/loop_detection.py +262 -0
- agents/task/agent/core/memory_prefetch.py +325 -0
- agents/task/agent/core/memory_writer.py +667 -0
- agents/task/agent/core/model_introspection.py +82 -0
- agents/task/agent/core/model_swap.py +177 -0
- agents/task/agent/core/next_action_internal.py +1331 -0
- agents/task/agent/core/output_validation.py +303 -0
- agents/task/agent/core/project_context.py +272 -0
- agents/task/agent/core/resources.py +93 -0
- agents/task/agent/core/result_processing.py +389 -0
- agents/task/agent/core/run_loop.py +661 -0
- agents/task/agent/core/safety_lifecycle.py +228 -0
- agents/task/agent/core/secret_guard.py +293 -0
- agents/task/agent/core/self_wake.py +280 -0
- agents/task/agent/core/session_metadata.py +104 -0
- agents/task/agent/core/step.py +781 -0
- agents/task/agent/core/step_execution.py +398 -0
- agents/task/agent/core/step_telemetry.py +222 -0
- agents/task/agent/core/test_aux_metering.py +50 -0
- agents/task/agent/core/test_billing_failover.py +68 -0
- agents/task/agent/core/test_fatal_error_classifier.py +38 -0
- agents/task/agent/core/test_judge_validation.py +275 -0
- agents/task/agent/core/test_model_swap.py +190 -0
- agents/task/agent/core/test_reflection_offload.py +54 -0
- agents/task/agent/core/turn_input.py +8 -0
- agents/task/agent/core/untrusted_wrap.py +103 -0
- agents/task/agent/core/user_ingress.py +228 -0
- agents/task/agent/hitl_manager.py +396 -0
- agents/task/agent/log_sanitize.py +36 -0
- agents/task/agent/message_manager/config.py +97 -0
- agents/task/agent/message_manager/service.py +758 -0
- agents/task/agent/message_manager/tests.py +305 -0
- agents/task/agent/message_manager/tool_call_builder.py +539 -0
- agents/task/agent/message_manager/tool_message_repair.py +364 -0
- agents/task/agent/message_manager/views.py +143 -0
- agents/task/agent/messages/__init__.py +8 -0
- agents/task/agent/messages/builders.py +309 -0
- agents/task/agent/messages/compactor.py +611 -0
- agents/task/agent/messages/context_references.py +271 -0
- agents/task/agent/messages/filters.py +333 -0
- agents/task/agent/messages/guidance.py +328 -0
- agents/task/agent/messages/persistence.py +448 -0
- agents/task/agent/messages/retrieval.py +506 -0
- agents/task/agent/messages/sqlite_persistence.py +85 -0
- agents/task/agent/messages/test_compaction_upgrade.py +349 -0
- agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
- agents/task/agent/messages/test_sqlite_persistence.py +54 -0
- agents/task/agent/messages/token_counter.py +532 -0
- agents/task/agent/orchestrator.py +776 -0
- agents/task/agent/profile_manager.py +105 -0
- agents/task/agent/profile_registry.py +173 -0
- agents/task/agent/prompts.py +1090 -0
- agents/task/agent/scenario_registry.py +226 -0
- agents/task/agent/service.py +509 -0
- agents/task/agent/session.py +984 -0
- agents/task/agent/skill_discovery.py +110 -0
- agents/task/agent/skill_frontmatter.py +94 -0
- agents/task/agent/skill_manager.py +1201 -0
- agents/task/agent/skill_store.py +627 -0
- agents/task/agent/skill_validation.py +56 -0
- agents/task/agent/skill_writer.py +492 -0
- agents/task/agent/sub_agent_manager.py +1137 -0
- agents/task/agent/test_session_disk_recovery.py +34 -0
- agents/task/agent/tests.py +513 -0
- agents/task/agent/tool_call_tracker.py +511 -0
- agents/task/agent/views.py +469 -0
- agents/task/config.py +432 -0
- agents/task/constants.py +1384 -0
- agents/task/flag_defaults.py +63 -0
- agents/task/goals/__init__.py +1 -0
- agents/task/goals/autonomy_marker.py +28 -0
- agents/task/goals/board.py +690 -0
- agents/task/goals/completion_judge.py +247 -0
- agents/task/goals/context.py +62 -0
- agents/task/goals/dispatcher.py +848 -0
- agents/task/goals/escalation.py +102 -0
- agents/task/goals/planner.py +114 -0
- agents/task/logging_config.py +155 -0
- agents/task/path.py +991 -0
- agents/task/robust_parse_config.py +400 -0
- agents/task/runtime/__init__.py +0 -0
- agents/task/runtime/run_as_session.py +130 -0
- agents/task/runtime_safety.py +308 -0
- agents/task/session/__init__.py +5 -0
- agents/task/session/browser_pool.py +130 -0
- agents/task/session/cleanup.py +612 -0
- agents/task/session/execution.py +397 -0
- agents/task/session/feed.py +200 -0
- agents/task/session/hitl_ingress.py +201 -0
- agents/task/session/hooks.py +83 -0
- agents/task/session/multi_agent.py +243 -0
- agents/task/session/workspace.py +91 -0
- agents/task/session_registry.py +91 -0
- agents/task/session_route.py +35 -0
- agents/task/sqlite_session_registry.py +261 -0
- agents/task/surface_config.py +185 -0
- agents/task/telemetry/__init__.py +58 -0
- agents/task/telemetry/event_log.py +180 -0
- agents/task/telemetry/formatters.py +642 -0
- agents/task/telemetry/manager.py +571 -0
- agents/task/telemetry/memory_events.py +66 -0
- agents/task/telemetry/self_events.py +53 -0
- agents/task/telemetry/sequence.py +160 -0
- agents/task/telemetry/service.py +1265 -0
- agents/task/telemetry/views.py +882 -0
- agents/task/templates.py +98 -0
- agents/task/test_aux_chain.py +151 -0
- agents/task/test_aux_router.py +41 -0
- agents/task/tests/test_parse_robustness.py +170 -0
- agents/task/tool_defaults.py +110 -0
- agents/task/utils.py +762 -0
- agents/task/utils_json.py +768 -0
- agents/task/utils_webview.py +86 -0
- agents/task/workspace_context.py +229 -0
- agents/task_agent_lite.py +2398 -0
- api/README.md +683 -0
- api/TASK_API_DOCS.md +151 -0
- api/__init__.py +0 -0
- api/a2a/__init__.py +37 -0
- api/a2a/agent_card.py +337 -0
- api/a2a/client.py +657 -0
- api/a2a/endpoints.py +375 -0
- api/a2a/models.py +268 -0
- api/a2a/streaming.py +419 -0
- api/a2a/task_handler.py +929 -0
- api/admin_endpoints.py +1584 -0
- api/app.py +923 -0
- api/auth_constants.py +28 -0
- api/auth_endpoints.py +402 -0
- api/auth_state.py +36 -0
- api/chat_via_task.py +42 -0
- api/conversation_manager.py +231 -0
- api/dependencies.py +186 -0
- api/eip8004_endpoints.py +452 -0
- api/hyperliquid_models.py +174 -0
- api/hyperliquid_routes.py +450 -0
- api/interfaces.py +207 -0
- api/jwt_middleware.py +83 -0
- api/kb/__init__.py +1 -0
- api/kb/endpoints.py +314 -0
- api/mcp_models.py +279 -0
- api/mcp_routes.py +438 -0
- api/middleware.py +526 -0
- api/models.py +176 -0
- api/openai_compat/__init__.py +0 -0
- api/openai_compat/model_map.py +25 -0
- api/openai_compat/models.py +59 -0
- api/openai_compat/router.py +140 -0
- api/payment_endpoints.py +444 -0
- api/payment_verification.py +138 -0
- api/polymarket_models.py +155 -0
- api/polymarket_routes.py +501 -0
- api/pricing_endpoints.py +89 -0
- api/session_routing.py +54 -0
- api/skill_endpoints.py +453 -0
- api/task_http_api.py +1856 -0
- api/webhooks.py +52 -0
- api/x402_endpoints.py +280 -0
- avatar/README.md +44 -0
- avatar/__init__.py +1 -0
- avatar/config/rob.json +7 -0
- avatar/mindprint.js +395 -0
- avatar/renders/rob.meta.json +20 -0
- avatar/renders/rob.png +0 -0
- avatar/studio.html +373 -0
- avatar/webview/avatar-live.js +44 -0
- cli/README.md +89 -0
- cli/__init__.py +0 -0
- cli/cli_surface.py +67 -0
- cli/commands/__init__.py +0 -0
- cli/commands/_bootstrap.py +49 -0
- cli/commands/_errors.py +53 -0
- cli/commands/chat.py +973 -0
- cli/commands/config.py +100 -0
- cli/commands/dashboard.py +103 -0
- cli/commands/doctor.py +222 -0
- cli/commands/email.py +174 -0
- cli/commands/gateway.py +346 -0
- cli/commands/goals.py +509 -0
- cli/commands/init.py +276 -0
- cli/commands/journey.py +30 -0
- cli/commands/kb.py +280 -0
- cli/commands/model.py +107 -0
- cli/commands/owner.py +431 -0
- cli/commands/pfp.py +232 -0
- cli/commands/run.py +414 -0
- cli/commands/serve.py +41 -0
- cli/commands/session.py +684 -0
- cli/commands/skill_install.py +786 -0
- cli/commands/skills.py +128 -0
- cli/commands/subagents.py +100 -0
- cli/commands/surface.py +81 -0
- cli/commands/telegram.py +185 -0
- cli/commands/todos.py +209 -0
- cli/commands/tools.py +102 -0
- cli/commands/update.py +316 -0
- cli/commands/whatsapp.py +175 -0
- cli/config_store.py +276 -0
- cli/gitignore.py +43 -0
- cli/inventory.py +46 -0
- cli/keys.py +101 -0
- cli/persona.py +17 -0
- cli/polyrob.py +137 -0
- cli/ui/__init__.py +64 -0
- cli/ui/activity.py +141 -0
- cli/ui/app.py +538 -0
- cli/ui/banner.py +233 -0
- cli/ui/blocks.py +534 -0
- cli/ui/bootstrap_notice.py +47 -0
- cli/ui/commands/__init__.py +41 -0
- cli/ui/commands/h_cron.py +64 -0
- cli/ui/commands/h_journey.py +164 -0
- cli/ui/commands/h_kb.py +121 -0
- cli/ui/commands/h_learn.py +100 -0
- cli/ui/commands/h_mcp.py +215 -0
- cli/ui/commands/h_self.py +113 -0
- cli/ui/commands/h_skills.py +314 -0
- cli/ui/commands/handlers.py +1672 -0
- cli/ui/commands/registry.py +426 -0
- cli/ui/dialog.py +425 -0
- cli/ui/event_registry.py +110 -0
- cli/ui/event_specs.py +45 -0
- cli/ui/events.py +433 -0
- cli/ui/identity.py +15 -0
- cli/ui/lifecycle.py +159 -0
- cli/ui/live_hooks.py +43 -0
- cli/ui/model_selector.py +532 -0
- cli/ui/persistent_loop.py +181 -0
- cli/ui/pick.py +113 -0
- cli/ui/plain_renderer.py +351 -0
- cli/ui/renderer.py +384 -0
- cli/ui/rich_renderer.py +533 -0
- cli/ui/secrets.py +115 -0
- cli/ui/state.py +376 -0
- cli/ui/statusbar.py +231 -0
- cli/ui/streaming.py +254 -0
- cli/ui/terminal_render.py +105 -0
- cli/ui/theme.py +132 -0
- cli/update/__init__.py +6 -0
- cli/update/context.py +121 -0
- cli/update/detect.py +176 -0
- cli/update/engine.py +95 -0
- cli/update/migrate_guarded.py +55 -0
- cli/update/process_guard.py +215 -0
- cli/update/runners.py +78 -0
- cli/update/snapshot.py +270 -0
- cli/update/versions.py +205 -0
- core/README.md +377 -0
- core/__init__.py +174 -0
- core/assets.py +87 -0
- core/async_bridge.py +76 -0
- core/autonomy_runtime.py +259 -0
- core/base_component.py +302 -0
- core/bootstrap.py +681 -0
- core/bot.py +310 -0
- core/config.py +833 -0
- core/constants.py +126 -0
- core/container.py +462 -0
- core/db_manifest.py +101 -0
- core/embedding.py +57 -0
- core/env.py +39 -0
- core/exceptions.py +287 -0
- core/flags.py +144 -0
- core/flags_catalog.py +345 -0
- core/home_migration.py +62 -0
- core/identity.py +118 -0
- core/initialization.py +1004 -0
- core/instance.py +537 -0
- core/interactive_gate.py +96 -0
- core/logging.py +436 -0
- core/owner_doc_writer.py +176 -0
- core/pairing.py +193 -0
- core/path_safety.py +20 -0
- core/paths.py +27 -0
- core/payment_config.py +21 -0
- core/permissions.py +322 -0
- core/runtime_config.py +96 -0
- core/runtime_paths.py +135 -0
- core/seams.py +35 -0
- core/secret_scan.py +25 -0
- core/secret_scrub.py +71 -0
- core/secrets.py +35 -0
- core/security_logging_filter.py +148 -0
- core/self_context_writer.py +333 -0
- core/self_evolution.py +294 -0
- core/session_context.py +47 -0
- core/sqlite_util.py +59 -0
- core/surfaces/__init__.py +16 -0
- core/surfaces/access.py +118 -0
- core/surfaces/binding.py +88 -0
- core/surfaces/bootstrap.py +78 -0
- core/surfaces/circuit.py +168 -0
- core/surfaces/continuity.py +77 -0
- core/surfaces/correspondents.py +228 -0
- core/surfaces/dispatcher.py +216 -0
- core/surfaces/envelopes.py +75 -0
- core/surfaces/gc.py +30 -0
- core/surfaces/idempotency.py +57 -0
- core/surfaces/inbound_webhook.py +124 -0
- core/surfaces/media.py +32 -0
- core/surfaces/message_router.py +91 -0
- core/surfaces/outbound_allowlist.py +80 -0
- core/surfaces/outbound_dispatcher.py +110 -0
- core/surfaces/outbound_mirror.py +37 -0
- core/surfaces/outbound_queue.py +107 -0
- core/surfaces/outbound_target.py +12 -0
- core/surfaces/owner_admin.py +29 -0
- core/surfaces/proactive.py +78 -0
- core/surfaces/progress.py +108 -0
- core/surfaces/rate_bucket.py +21 -0
- core/surfaces/registry.py +51 -0
- core/surfaces/rendering.py +53 -0
- core/surfaces/send_policy.py +14 -0
- core/surfaces/serialize.py +24 -0
- core/surfaces/session_chat_registry.py +112 -0
- core/surfaces/session_policy.py +60 -0
- core/surfaces/surface.py +288 -0
- core/surfaces/transcription.py +88 -0
- core/surfaces/voice_echo.py +26 -0
- core/surfaces/voice_guard.py +23 -0
- core/tickers.py +174 -0
- core/tool_catalog.py +192 -0
- core/version.py +75 -0
- core/wallet/__init__.py +5 -0
- core/wallet/agent_wallet.py +59 -0
- core/wallet/audit_sink.py +61 -0
- core/wallet/config.py +70 -0
- core/wallet/factory.py +87 -0
- core/wallet/policy.py +104 -0
- core/wallet/signer.py +51 -0
- cron/README.md +65 -0
- cron/__init__.py +0 -0
- cron/delivery.py +277 -0
- cron/digest.py +124 -0
- cron/jobs.py +207 -0
- cron/runner.py +297 -0
- cron/schedule.py +194 -0
- cron/scheduler.py +201 -0
- cron/service.py +57 -0
- cron/wake_gate.py +223 -0
- data/__init__.py +1 -0
- data/prompts/autov2_prompts.json +87 -0
- data/prompts/skills/browser-automation/SKILL.md +43 -0
- data/prompts/skills/coding-workflow/SKILL.md +36 -0
- data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
- data/prompts/skills/document-writing/SKILL.md +33 -0
- data/prompts/skills/email-comms/SKILL.md +33 -0
- data/prompts/skills/file-data-ops/SKILL.md +35 -0
- data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
- data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
- data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
- data/prompts/skills/lead-research/SKILL.md +86 -0
- data/prompts/skills/market-research-brief/SKILL.md +104 -0
- data/prompts/skills/person-analyzer/SKILL.md +124 -0
- data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
- data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
- data/prompts/skills/polymarket-trading/SKILL.md +55 -0
- data/prompts/skills/presentation-creator/SKILL.md +36 -0
- data/prompts/skills/project-analyzer/SKILL.md +169 -0
- data/prompts/skills/rules.json +268 -0
- data/prompts/skills/secret-handling/SKILL.md +20 -0
- data/prompts/skills/skill-authoring/SKILL.md +31 -0
- data/prompts/skills/skill-security-review/SKILL.md +27 -0
- data/prompts/skills/social-discovery/SKILL.md +57 -0
- data/prompts/skills/task-planning/SKILL.md +33 -0
- data/prompts/skills/web-research/SKILL.md +39 -0
- data/prompts/skills/web-scraping/SKILL.md +73 -0
- data/prompts/skills/x-engagement/SKILL.md +69 -0
- data/prompts/system_prompts.json +4 -0
- data/subscription_channels.json +5 -0
- modules/README.md +503 -0
- modules/__init__.py +124 -0
- modules/auth/__init__.py +13 -0
- modules/auth/api_key_manager.py +157 -0
- modules/auth/identity_mapper.py +380 -0
- modules/auth/siwe_auth.py +273 -0
- modules/auth/tier_manager.py +173 -0
- modules/base_module.py +190 -0
- modules/credits/__init__.py +29 -0
- modules/credits/balance_manager.py +237 -0
- modules/credits/cost_utils.py +129 -0
- modules/credits/pricing.py +139 -0
- modules/credits/unified_ledger.py +148 -0
- modules/credits/usage_meter.py +310 -0
- modules/credits/usage_tracker.py +674 -0
- modules/database/__init__.py +33 -0
- modules/database/audit_log.py +322 -0
- modules/database/auth_tables.py +276 -0
- modules/database/connection.py +644 -0
- modules/database/connection_pool.py +305 -0
- modules/database/conversation_contexts.py +446 -0
- modules/database/database_manager.py +237 -0
- modules/database/hyperliquid.py +375 -0
- modules/database/polymarket.py +574 -0
- modules/database/user_mcp_servers.py +728 -0
- modules/database/user_profiles.py +562 -0
- modules/database/utils.py +107 -0
- modules/database/x402_tables.py +92 -0
- modules/eip8004/README.md +1069 -0
- modules/eip8004/__init__.py +65 -0
- modules/eip8004/contracts.py +498 -0
- modules/eip8004/models.py +271 -0
- modules/eip8004/registration.py +148 -0
- modules/eip8004/reputation.py +367 -0
- modules/eip8004/validation.py +277 -0
- modules/llm/__init__.py +97 -0
- modules/llm/adapters.py +963 -0
- modules/llm/anthropic_client.py +831 -0
- modules/llm/available_models.py +106 -0
- modules/llm/brain_scrubber.py +137 -0
- modules/llm/cache_hints.py +162 -0
- modules/llm/deepseek_client.py +562 -0
- modules/llm/gemini_client.py +1367 -0
- modules/llm/llm_client.py +1139 -0
- modules/llm/llm_client_registry.py +188 -0
- modules/llm/llm_factory.py +237 -0
- modules/llm/llm_manager.py +1036 -0
- modules/llm/messages.py +374 -0
- modules/llm/model_registry.py +1935 -0
- modules/llm/nvidia_client.py +125 -0
- modules/llm/openai_client.py +800 -0
- modules/llm/openrouter_client.py +969 -0
- modules/llm/profiles.py +178 -0
- modules/llm/test_anthropic_cache_capture.py +35 -0
- modules/llm/test_cached_pricing_derivation.py +24 -0
- modules/llm/think_scrubber.py +444 -0
- modules/llm/token_counter.py +268 -0
- modules/memory/__init__.py +35 -0
- modules/memory/backend_factory.py +83 -0
- modules/memory/cache_manager.py +186 -0
- modules/memory/episodic.py +108 -0
- modules/memory/local_vector_memory_provider.py +552 -0
- modules/memory/memory_manager.py +225 -0
- modules/memory/models.py +312 -0
- modules/memory/provider.py +210 -0
- modules/memory/registry.py +205 -0
- modules/memory/sqlite_memory_provider.py +767 -0
- modules/memory/task/__init__.py +52 -0
- modules/memory/task/compaction_manager.py +157 -0
- modules/memory/task/context_retriever.py +809 -0
- modules/memory/task/hierarchical_memory.py +1002 -0
- modules/memory/task/lexical_retriever.py +71 -0
- modules/memory/task/null_context_manager.py +213 -0
- modules/memory/task/phase_manager.py +625 -0
- modules/memory/task/reflection_service.py +157 -0
- modules/memory/task/semantic_retriever.py +218 -0
- modules/memory/task/task_context_manager.py +1165 -0
- modules/memory/task/test_forgetting_engages.py +24 -0
- modules/memory/task/test_reflection_llm.py +39 -0
- modules/memory/task/test_threat_scan.py +53 -0
- modules/memory/task/threat_scan.py +90 -0
- modules/memory/test_sqlite_memory_provider.py +106 -0
- modules/memory/user_profile_manager.py +527 -0
- modules/payments/__init__.py +11 -0
- modules/payments/deposit_monitor.py +478 -0
- modules/payments/price_oracle.py +70 -0
- modules/payments/treasury_sweeper.py +401 -0
- modules/payments/wallet_generator.py +145 -0
- modules/pfp/__init__.py +1 -0
- modules/pfp/config.py +92 -0
- modules/pfp/mesh.py +565 -0
- modules/pfp/push.py +69 -0
- modules/pfp/renderer.py +85 -0
- modules/pfp/store.py +82 -0
- modules/skills/__init__.py +1 -0
- modules/skills/skill_usage.py +248 -0
- modules/transcription/__init__.py +24 -0
- modules/transcription/base.py +20 -0
- modules/transcription/faster_whisper_transcriber.py +55 -0
- modules/x402/README.md +249 -0
- modules/x402/__init__.py +22 -0
- modules/x402/invoicing.py +416 -0
- modules/x402/middleware.py +364 -0
- modules/x402/settlement_watcher.py +145 -0
- modules/x402/x402_integration.py +344 -0
- polyrob-0.5.0.dist-info/METADATA +482 -0
- polyrob-0.5.0.dist-info/RECORD +745 -0
- polyrob-0.5.0.dist-info/WHEEL +5 -0
- polyrob-0.5.0.dist-info/entry_points.txt +2 -0
- polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
- polyrob-0.5.0.dist-info/top_level.txt +12 -0
- surfaces/README.md +70 -0
- surfaces/__init__.py +0 -0
- surfaces/email/__init__.py +0 -0
- surfaces/email/dedup.py +44 -0
- surfaces/email/harness.py +187 -0
- surfaces/email/inbound.py +159 -0
- surfaces/email/seed.py +60 -0
- surfaces/email/surface.py +105 -0
- surfaces/telegram/__init__.py +0 -0
- surfaces/telegram/dedup.py +83 -0
- surfaces/telegram/harness.py +886 -0
- surfaces/telegram/inbound.py +145 -0
- surfaces/telegram/interactive_tools.py +49 -0
- surfaces/telegram/markdown.py +347 -0
- surfaces/telegram/rate_limit.py +167 -0
- surfaces/telegram/surface.py +187 -0
- surfaces/telegram/voice.py +52 -0
- surfaces/whatsapp/__init__.py +0 -0
- surfaces/whatsapp/client.py +51 -0
- surfaces/whatsapp/harness.py +62 -0
- surfaces/whatsapp/inbound.py +132 -0
- surfaces/whatsapp/surface.py +64 -0
- surfaces/whatsapp/window.py +34 -0
- tools/README.md +578 -0
- tools/__init__.py +424 -0
- tools/alchemy/__init__.py +5 -0
- tools/alchemy/alchemy_tool.py +389 -0
- tools/anysite/__init__.py +17 -0
- tools/anysite/client.py +88 -0
- tools/anysite/tool.py +89 -0
- tools/base_tool.py +468 -0
- tools/browser/__init__.py +22 -0
- tools/browser/actions.py +118 -0
- tools/browser/browser.py +1457 -0
- tools/browser/browser_manager.py +690 -0
- tools/browser/context.py +1748 -0
- tools/browser/playwright_utils.py +137 -0
- tools/browser/views.py +56 -0
- tools/code_exec/SANDBOX_SECURITY.md +144 -0
- tools/code_exec/__init__.py +178 -0
- tools/code_exec/backend.py +73 -0
- tools/code_exec/backends/__init__.py +1 -0
- tools/code_exec/backends/docker.py +779 -0
- tools/code_exec/backends/local_subprocess.py +154 -0
- tools/code_exec/env_policy.py +45 -0
- tools/code_exec/result.py +42 -0
- tools/code_exec/sandbox_guard.py +59 -0
- tools/code_exec/tool.py +227 -0
- tools/coding/__init__.py +50 -0
- tools/coding/edit.py +107 -0
- tools/coding/search.py +113 -0
- tools/coding/tool.py +353 -0
- tools/collabland/__init__.py +5 -0
- tools/collabland/collabland_tool.py +471 -0
- tools/controller/__init__.py +0 -0
- tools/controller/_helpers.py +165 -0
- tools/controller/action_registration.py +1765 -0
- tools/controller/approval.py +244 -0
- tools/controller/approval_interactive.py +90 -0
- tools/controller/delegation.py +195 -0
- tools/controller/execution.py +797 -0
- tools/controller/execution_context.py +141 -0
- tools/controller/hooks.py +135 -0
- tools/controller/introspection.py +344 -0
- tools/controller/mcp_registrar.py +190 -0
- tools/controller/message_send.py +31 -0
- tools/controller/registry/__init__.py +4 -0
- tools/controller/registry/schema_generators.py +395 -0
- tools/controller/registry/schema_sanitizer.py +406 -0
- tools/controller/registry/service.py +1266 -0
- tools/controller/registry/views.py +151 -0
- tools/controller/service.py +409 -0
- tools/controller/tool_management.py +334 -0
- tools/controller/types.py +28 -0
- tools/controller/views.py +450 -0
- tools/cronjob_tools.py +166 -0
- tools/crypto_trade_gate.py +59 -0
- tools/descriptors.py +452 -0
- tools/dom/__init__.py +0 -0
- tools/dom/history_tree_processor/service.py +244 -0
- tools/dom/history_tree_processor/view.py +99 -0
- tools/dom/service.py +471 -0
- tools/dom/views.py +346 -0
- tools/email_tool.py +403 -0
- tools/exceptions.py +169 -0
- tools/filesystem.py +1038 -0
- tools/filesystem_docproc.py +331 -0
- tools/filesystem_pdf.py +755 -0
- tools/git/__init__.py +42 -0
- tools/git/tool.py +258 -0
- tools/github/__init__.py +33 -0
- tools/github/client.py +94 -0
- tools/github/tool.py +237 -0
- tools/goal_tools.py +309 -0
- tools/hyperliquid/__init__.py +56 -0
- tools/hyperliquid/models.py +233 -0
- tools/hyperliquid/service.py +1680 -0
- tools/knowledge_ingest.py +950 -0
- tools/mcp/README.md +535 -0
- tools/mcp/__init__.py +52 -0
- tools/mcp/catalog.py +115 -0
- tools/mcp/config.py +337 -0
- tools/mcp/mcp_tool.py +1415 -0
- tools/mcp/param_coercion.py +289 -0
- tools/mcp/protocol.py +1520 -0
- tools/mcp/rate_limit.py +50 -0
- tools/mcp/security.py +424 -0
- tools/mcp/self_install.py +111 -0
- tools/mcp/server_manager.py +829 -0
- tools/mcp/subscriptions.py +72 -0
- tools/mcp/user_mcp_service.py +899 -0
- tools/mcp/validation_tracker.py +89 -0
- tools/mcp/views.py +346 -0
- tools/oauth/__init__.py +17 -0
- tools/oauth/manager.py +75 -0
- tools/oauth/provider.py +69 -0
- tools/oauth/providers/__init__.py +1 -0
- tools/oauth/providers/generic_oauth2.py +96 -0
- tools/perplexity_tool.py +280 -0
- tools/polymarket/__init__.py +41 -0
- tools/polymarket/clob_adapter.py +76 -0
- tools/polymarket/models.py +182 -0
- tools/polymarket/service.py +2082 -0
- tools/self_env/__init__.py +61 -0
- tools/self_env/tool.py +274 -0
- tools/shell/__init__.py +80 -0
- tools/shell/backend_pool.py +118 -0
- tools/shell/discipline.py +62 -0
- tools/shell/executor.py +157 -0
- tools/shell/loopback_allow.py +87 -0
- tools/shell/process_registry.py +113 -0
- tools/shell/process_tool.py +131 -0
- tools/shell/state.py +129 -0
- tools/shell/tool.py +151 -0
- tools/task_tool.py +637 -0
- tools/twitter_tool.py +2086 -0
- tools/user_directory.py +179 -0
- tools/web_fetch/__init__.py +3 -0
- tools/web_fetch/fetcher.py +144 -0
- tools/web_fetch/render.py +44 -0
- tools/web_fetch/tool.py +60 -0
- tools/x402/__init__.py +60 -0
- tools/x402/client.py +47 -0
- tools/x402/invoice_tool.py +143 -0
- tools/x402/real_client.py +250 -0
- tools/x402/service.py +131 -0
- utils/README.md +498 -0
- utils/__init__.py +46 -0
- utils/auth_utils.py +114 -0
- utils/bounded_collections.py +162 -0
- utils/circuit_breaker.py +389 -0
- utils/gif_utils.py +435 -0
- utils/markdown_utils.py +291 -0
- utils/message_utils.py +147 -0
- utils/metrics.py +36 -0
- utils/path_validator.py +227 -0
- utils/rate_limit_manager.py +399 -0
- utils/result_size.py +296 -0
- utils/time_utils.py +206 -0
- utils/user_utils.py +188 -0
- webview/README.md +111 -0
- webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
- webview/__init__.py +8 -0
- webview/activity.py +636 -0
- webview/owner_auth.py +108 -0
- webview/pages.py +448 -0
- webview/repair_sessions.py +399 -0
- webview/server.py +4323 -0
- webview/server_launcher.py +112 -0
- webview/static/css/activity.css +89 -0
- webview/static/css/chat.css +1772 -0
- webview/static/css/components.css +1190 -0
- webview/static/css/config-panel.css +651 -0
- webview/static/css/pages/admin.css +766 -0
- webview/static/css/pages/profile.css +231 -0
- webview/static/css/pages/settings.css +297 -0
- webview/static/css/pages/signin.css +316 -0
- webview/static/css/style.css +4250 -0
- webview/static/css/variables.css +147 -0
- webview/static/css/workspace-fullwidth-fix.css +109 -0
- webview/static/img/favicon.ico +0 -0
- webview/static/js/activity.js +394 -0
- webview/static/js/admin/activity.js +186 -0
- webview/static/js/admin/dashboard.js +163 -0
- webview/static/js/admin/user_detail.js +400 -0
- webview/static/js/admin/users.js +174 -0
- webview/static/js/admin/utils.js +214 -0
- webview/static/js/chat.js +3178 -0
- webview/static/js/config-panel.js +522 -0
- webview/static/js/constants.js +20 -0
- webview/static/js/error-handler.js +226 -0
- webview/static/js/ethers.min.js +1 -0
- webview/static/js/event-filter.js +137 -0
- webview/static/js/event-store.js +351 -0
- webview/static/js/file-attachments.js +374 -0
- webview/static/js/file-loader.js +163 -0
- webview/static/js/index.js +103 -0
- webview/static/js/performance-utils.js +196 -0
- webview/static/js/profile.js +396 -0
- webview/static/js/screenshot.js +386 -0
- webview/static/js/session.js +1527 -0
- webview/static/js/settings.js +1288 -0
- webview/static/js/sidebar-data.js +766 -0
- webview/static/js/sidebar-toggle.js +296 -0
- webview/static/js/socket.io.min.js +7 -0
- webview/static/js/stats.js +355 -0
- webview/static/js/ui-utils.js +420 -0
- webview/static/js/workspace.js +966 -0
- webview/stats_service.py +613 -0
- webview/templates/__init__.py +1 -0
- webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- webview/templates/activity.html +36 -0
- webview/templates/admin/activity.html +122 -0
- webview/templates/admin/dashboard.html +112 -0
- webview/templates/admin/user_detail.html +228 -0
- webview/templates/admin/users.html +105 -0
- webview/templates/autonomy.html +60 -0
- webview/templates/error.html +133 -0
- webview/templates/finance.html +90 -0
- webview/templates/identity.html +73 -0
- webview/templates/index.html +42 -0
- webview/templates/layout.html +125 -0
- webview/templates/memory.html +59 -0
- webview/templates/owner_login.html +15 -0
- webview/templates/profile.html +121 -0
- webview/templates/session.html +312 -0
- webview/templates/settings.html +534 -0
- webview/templates/sidebar.html +112 -0
- webview/templates/signin.html +213 -0
- webview/templates/status.html +14 -0
- webview/templates/system.html +45 -0
- 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)
|