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
agents/task/constants.py
ADDED
|
@@ -0,0 +1,1384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralized constants for the Task agent system.
|
|
3
|
+
This provides a single source of truth for history management, trimming, and memory limits.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from core.env import bool_env as _core_bool_env
|
|
9
|
+
|
|
10
|
+
# Token counting defaults - Use modules.llm.token_counter for actual counting
|
|
11
|
+
# IMG_TOKENS kept for backward compatibility but should use token_counter._count_multimodal_tokens
|
|
12
|
+
IMG_TOKENS = 800 # Default estimate for image tokens (actual counting done by modules.llm)
|
|
13
|
+
DEFAULT_IMG_TOKENS = IMG_TOKENS # Alias for compatibility
|
|
14
|
+
ESTIMATED_CHARS_PER_TOKEN = 4 # Updated to match model_registry defaults
|
|
15
|
+
|
|
16
|
+
# Default user ID for anonymous sessions. The canonical token lives in
|
|
17
|
+
# core.identity (the tenant SSOT); aliased here so the value is defined once.
|
|
18
|
+
from core.identity import ANON_USER_ID as DEFAULT_USER_ID # noqa: E402
|
|
19
|
+
|
|
20
|
+
# Token recalibration
|
|
21
|
+
CALIBRATION_CHECK_INTERVAL = 5 # Recalibrate token counts every N steps
|
|
22
|
+
|
|
23
|
+
# Error message display
|
|
24
|
+
ERROR_PREVIEW_COUNT = 3 # Number of errors to show in previews
|
|
25
|
+
|
|
26
|
+
# Loop detection configuration
|
|
27
|
+
class LoopDetectionConfig:
|
|
28
|
+
"""Centralized loop detection configuration - Optimized for reliability"""
|
|
29
|
+
# Development mode: tighter thresholds for faster loop detection
|
|
30
|
+
IS_DEVELOPMENT = os.getenv('ENVIRONMENT', 'development') == 'development'
|
|
31
|
+
|
|
32
|
+
# Use tighter thresholds in development for faster debugging
|
|
33
|
+
if IS_DEVELOPMENT:
|
|
34
|
+
MAX_REPETITIONS = int(os.getenv('MAX_REPETITIONS', '2')) # FIX 4: Aggressive loop detection
|
|
35
|
+
UNCHANGED_STATE_THRESHOLD = int(os.getenv('UNCHANGED_STATE_THRESHOLD', '3')) # FIX 4: Aggressive
|
|
36
|
+
STATE_CHANGE_THRESHOLD = 3 # FIX 4: Detect loops after 2-3 repetitions
|
|
37
|
+
MAX_ALLOWED_REPETITIONS = 2 # FIX 4: Catch loops after just 2 repetitions
|
|
38
|
+
else:
|
|
39
|
+
MAX_REPETITIONS = int(os.getenv('MAX_REPETITIONS', '3')) # FIX 4: Stricter for production
|
|
40
|
+
UNCHANGED_STATE_THRESHOLD = int(os.getenv('UNCHANGED_STATE_THRESHOLD', '4')) # FIX 4: Stricter
|
|
41
|
+
STATE_CHANGE_THRESHOLD = 4 # FIX 4: Lower threshold
|
|
42
|
+
MAX_ALLOWED_REPETITIONS = 3 # FIX 4: Catch loops faster in production
|
|
43
|
+
|
|
44
|
+
ACTION_SIMILARITY_THRESHOLD = 0.95 # Strict matching to avoid false positives
|
|
45
|
+
MEMORY_WINDOW_SIZE = 25 # Larger window for better context
|
|
46
|
+
DETECTION_WINDOW = 15 # Longer detection window
|
|
47
|
+
|
|
48
|
+
# Aliases for compatibility
|
|
49
|
+
LOOP_WARNING_THRESHOLD = MAX_ALLOWED_REPETITIONS # Threshold for warning about loops
|
|
50
|
+
ALTERNATING_PATTERN_MIN = 4 # Minimum pattern length to detect alternating actions
|
|
51
|
+
|
|
52
|
+
# Memory management configuration
|
|
53
|
+
class MemoryConfig:
|
|
54
|
+
"""Memory optimization settings for AutoV2 - Optimized for performance"""
|
|
55
|
+
MAX_SCREENSHOTS_IN_GIF = 10 # Reduced to prevent OOM
|
|
56
|
+
MAX_HISTORY_SIZE = int(os.getenv('MAX_MEMORY_CACHE_SIZE', '30')) # Configurable via env
|
|
57
|
+
SCREENSHOT_JPEG_QUALITY = int(os.getenv('SCREENSHOT_JPEG_QUALITY', '70')) # Configurable
|
|
58
|
+
ENABLE_GIF_CREATION = _core_bool_env('ENABLE_GIF_CREATION', False) # Disabled by default
|
|
59
|
+
CLEAR_SCREENSHOTS_AFTER_GIF = True # Remove base64 data after GIF creation
|
|
60
|
+
MAX_SCREENSHOT_SIZE_MB = 3 # Reduced max size per screenshot
|
|
61
|
+
# UNIFIED: Single source from environment, default 50
|
|
62
|
+
CLEANUP_INTERVAL = int(os.getenv('MEMORY_CLEANUP_INTERVAL', '50')) # Clean every N operations
|
|
63
|
+
|
|
64
|
+
# History and Message Management
|
|
65
|
+
MAX_HISTORY_SIZE = 30 # Maximum number of messages to keep in history
|
|
66
|
+
PROTECT_RECENT_MESSAGES = 10 # Number of recent messages to protect from trimming
|
|
67
|
+
PROTECT_INITIAL_MESSAGES = 3 # Number of initial messages (system prompts) to protect
|
|
68
|
+
|
|
69
|
+
# Token and Context Management - REMOVED: Use modules.llm.model_registry instead
|
|
70
|
+
# TOKEN_BUFFER_SIZE, MIN_CONTEXT_TOKENS, MAX_CONTEXT_PERCENTAGE moved to model_registry
|
|
71
|
+
# For context management, use: get_model_config(model_name).safe_input_tokens
|
|
72
|
+
|
|
73
|
+
# Tool Call Management
|
|
74
|
+
# Dynamic lookahead window that scales with context size
|
|
75
|
+
def get_tool_lookahead_window(total_messages: int = 0) -> int:
|
|
76
|
+
"""Get dynamic lookahead window based on context size.
|
|
77
|
+
|
|
78
|
+
For large contexts (1M+ tokens), tool responses may be far from their calls.
|
|
79
|
+
This scales the window appropriately to handle million-token contexts.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
total_messages: Total number of messages in context
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Lookahead window size
|
|
86
|
+
"""
|
|
87
|
+
if total_messages > 10000: # Extreme scale (2M+ tokens)
|
|
88
|
+
return 1000
|
|
89
|
+
elif total_messages > 5000: # Million-token scale contexts
|
|
90
|
+
return 600
|
|
91
|
+
elif total_messages > 2000: # Very large context
|
|
92
|
+
return 300
|
|
93
|
+
elif total_messages > 1000: # Large context
|
|
94
|
+
return 150
|
|
95
|
+
elif total_messages > 500: # Medium-large context
|
|
96
|
+
return 75
|
|
97
|
+
elif total_messages > 200: # Medium context
|
|
98
|
+
return 40
|
|
99
|
+
else:
|
|
100
|
+
return 20 # Default for small contexts
|
|
101
|
+
|
|
102
|
+
TOOL_LOOKAHEAD_WINDOW = 15 # Default, use get_tool_lookahead_window() for dynamic sizing
|
|
103
|
+
PLACEHOLDER_CONTENT = "[Awaiting tool response]" # Content for placeholder ToolMessages
|
|
104
|
+
PLACEHOLDER_SOURCE = "tool_placeholder" # Metadata source for placeholders
|
|
105
|
+
|
|
106
|
+
# Memory and Performance
|
|
107
|
+
# REMOVED DUPLICATE: Use MemoryConfig.CLEANUP_INTERVAL instead
|
|
108
|
+
MEMORY_CHECK_INTERVAL = 10 # Check memory usage every N steps
|
|
109
|
+
MEMORY_MESSAGE_THRESHOLD_MB = 50 # Trim if message history uses > 50MB
|
|
110
|
+
MEMORY_HIGH_WATERMARK_MB = 1000 # Warn if total memory over 1GB
|
|
111
|
+
MAX_RESULT_PREVIEW_LENGTH = 500 # Maximum length for action result previews
|
|
112
|
+
LARGE_CONTENT_THRESHOLD = 50000 # Threshold for considering content "large"
|
|
113
|
+
|
|
114
|
+
# Retry and Timeout Settings
|
|
115
|
+
MAX_LLM_RETRIES = 3 # Maximum retries for LLM calls
|
|
116
|
+
MAX_LLM_CREATION_RETRIES = 2 # Retries when creating LLM instances
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ========== AUX-MODEL ROUTER (A5/A1 + generalized) ==========
|
|
120
|
+
# Provider -> cheap auxiliary model, used for compaction (`llm_compact_history`) and the
|
|
121
|
+
# judge aux task (output validation) via resolve_aux_model() below.
|
|
122
|
+
# NOTE: this map has no "openrouter" key (intentional) — an openrouter session's aux
|
|
123
|
+
# tasks resolve to None and fall back to the main model.
|
|
124
|
+
# (Reference parity: auxiliary.compression.model — a small/fast model for compaction.)
|
|
125
|
+
COMPACTION_AUX_MODEL_MAP = {
|
|
126
|
+
"anthropic": "claude-haiku-4-5",
|
|
127
|
+
"openai": "gpt-5-mini",
|
|
128
|
+
"gemini": "gemini-flash",
|
|
129
|
+
"google": "gemini-flash",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Alias: the generalized router reuses the same cheap-model map for all aux tasks.
|
|
133
|
+
AUX_MODEL_MAP = COMPACTION_AUX_MODEL_MAP
|
|
134
|
+
|
|
135
|
+
# Per-task explicit override env var names. Compaction keeps its legacy knob.
|
|
136
|
+
# UP-10 2.1: only `compaction` (live) and `judge` (wired into output validation,
|
|
137
|
+
# default-off/fail-open) remain. `planner`/`vision` had zero call sites and were
|
|
138
|
+
# removed as dead config surface.
|
|
139
|
+
_AUX_TASK_ENV = {
|
|
140
|
+
"compaction": "COMPACTION_MODEL",
|
|
141
|
+
"judge": "AUX_MODEL_JUDGE",
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def resolve_aux_model(task, provider=None):
|
|
146
|
+
"""Which model should aux `task` use? (None => use the main model.)
|
|
147
|
+
|
|
148
|
+
Precedence: 1) per-task explicit env (e.g. AUX_MODEL_JUDGE / COMPACTION_MODEL);
|
|
149
|
+
2) provider cheap-map default when AUX_AUTO=true (or, for compaction only, the
|
|
150
|
+
legacy COMPACTION_AUTO_AUX=true); 3) None.
|
|
151
|
+
"""
|
|
152
|
+
explicit = os.getenv(_AUX_TASK_ENV.get(task, ""), "")
|
|
153
|
+
if explicit:
|
|
154
|
+
return explicit
|
|
155
|
+
auto = _core_bool_env("AUX_AUTO", False)
|
|
156
|
+
if task == "compaction":
|
|
157
|
+
auto = auto or _core_bool_env("COMPACTION_AUTO_AUX", False)
|
|
158
|
+
if auto and provider:
|
|
159
|
+
return AUX_MODEL_MAP.get(provider.lower())
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_AUX_SLOTS = ("compaction", "judge", "reflection")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _parse_provider_model(token, default_provider=None):
|
|
167
|
+
"""Parse one fallback-chain token ('provider/model' or bare 'model')."""
|
|
168
|
+
token = (token or "").strip()
|
|
169
|
+
if not token:
|
|
170
|
+
return None
|
|
171
|
+
if "/" in token:
|
|
172
|
+
head, tail = token.split("/", 1)
|
|
173
|
+
tail = tail.strip()
|
|
174
|
+
head = head.strip()
|
|
175
|
+
return {"model": tail, "provider": head or default_provider} if tail else None
|
|
176
|
+
return {"model": token, "provider": default_provider}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def resolve_aux_chain(task, provider=None):
|
|
180
|
+
"""Ordered aux-model candidates for `task` (primary first, then per-task fallbacks).
|
|
181
|
+
|
|
182
|
+
B5 (Hermes `auxiliary.<task>.fallback_chain` parity): each of the 3 real aux
|
|
183
|
+
call sites (compaction/judge/reflection) can be pointed at its own model+
|
|
184
|
+
provider plus an ordered fallback list, instead of a single model string.
|
|
185
|
+
Empty list => caller uses the main model (unchanged runtime contract).
|
|
186
|
+
|
|
187
|
+
Env per slot (<SLOT> = task.upper()):
|
|
188
|
+
AUX_MODEL_<SLOT> primary model (legacy: COMPACTION_MODEL / AUX_MODEL_JUDGE)
|
|
189
|
+
AUX_PROVIDER_<SLOT> primary provider (legacy: COMPACTION_PROVIDER / AUX_PROVIDER)
|
|
190
|
+
AUX_FALLBACK_<SLOT> comma list of provider/model (or bare model) candidates
|
|
191
|
+
|
|
192
|
+
`reflection` inherits compaction's config (model AND provider, as a pair) when
|
|
193
|
+
its own env is unset (back-compat: reflection historically reused
|
|
194
|
+
_provision_compaction_llm wholesale). If reflection sets its OWN model, none of
|
|
195
|
+
compaction's config is consulted — including its provider, so a compaction
|
|
196
|
+
provider never silently leaks onto a reflection-specific model.
|
|
197
|
+
|
|
198
|
+
Provider precedence for reflection specifically:
|
|
199
|
+
1) AUX_PROVIDER_REFLECTION (explicit reflection override)
|
|
200
|
+
2) compaction's provider (AUX_PROVIDER_COMPACTION or legacy COMPACTION_PROVIDER)
|
|
201
|
+
— ONLY when reflection's model was itself inherited from compaction
|
|
202
|
+
3) AUX_PROVIDER (generic aux fallback)
|
|
203
|
+
"""
|
|
204
|
+
if task not in _AUX_SLOTS:
|
|
205
|
+
# Fixed slot set by design (the 3 real aux call sites) — an unknown task
|
|
206
|
+
# never gets a chain, even under AUX_AUTO (dead-config trap guard, UP-10).
|
|
207
|
+
return []
|
|
208
|
+
slot = task.upper()
|
|
209
|
+
inherited_from_compaction = False
|
|
210
|
+
if task == "reflection":
|
|
211
|
+
# Reflection is NOT in _AUX_TASK_ENV, so resolve_aux_model("reflection", ...)'s
|
|
212
|
+
# only possible effect is the global-AUX_AUTO cheap-map early-exit — which
|
|
213
|
+
# would pre-empt the compaction inheritance below and drop the paired
|
|
214
|
+
# COMPACTION_PROVIDER (pre-B5, reflection reused _provision_compaction_llm
|
|
215
|
+
# wholesale, so AUX_AUTO + COMPACTION_PROVIDER resolved as a pair). Go
|
|
216
|
+
# straight from own-env to the compaction branch, which itself honors
|
|
217
|
+
# AUX_AUTO/COMPACTION_AUTO_AUX via resolve_aux_model("compaction", ...).
|
|
218
|
+
primary_model = os.getenv("AUX_MODEL_REFLECTION", "")
|
|
219
|
+
if not primary_model:
|
|
220
|
+
primary_model = os.getenv("AUX_MODEL_COMPACTION", "") or resolve_aux_model("compaction", provider)
|
|
221
|
+
if primary_model:
|
|
222
|
+
inherited_from_compaction = True
|
|
223
|
+
else:
|
|
224
|
+
primary_model = os.getenv(f"AUX_MODEL_{slot}", "") or resolve_aux_model(task, provider)
|
|
225
|
+
if not primary_model:
|
|
226
|
+
return []
|
|
227
|
+
|
|
228
|
+
primary_provider = os.getenv(f"AUX_PROVIDER_{slot}") or None
|
|
229
|
+
if task == "compaction":
|
|
230
|
+
primary_provider = primary_provider or os.getenv("COMPACTION_PROVIDER") or None
|
|
231
|
+
elif task == "reflection":
|
|
232
|
+
if not primary_provider and inherited_from_compaction:
|
|
233
|
+
primary_provider = os.getenv("AUX_PROVIDER_COMPACTION") or os.getenv("COMPACTION_PROVIDER") or None
|
|
234
|
+
primary_provider = primary_provider or os.getenv("AUX_PROVIDER") or None
|
|
235
|
+
else:
|
|
236
|
+
primary_provider = primary_provider or os.getenv("AUX_PROVIDER") or None
|
|
237
|
+
|
|
238
|
+
chain = [{"model": primary_model, "provider": primary_provider}]
|
|
239
|
+
|
|
240
|
+
raw_fb = os.getenv(f"AUX_FALLBACK_{slot}", "")
|
|
241
|
+
if not raw_fb and task == "reflection" and inherited_from_compaction:
|
|
242
|
+
raw_fb = os.getenv("AUX_FALLBACK_COMPACTION", "")
|
|
243
|
+
for tok in raw_fb.split(","):
|
|
244
|
+
cand = _parse_provider_model(tok)
|
|
245
|
+
if cand:
|
|
246
|
+
chain.append(cand)
|
|
247
|
+
return chain
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def reflection_llm_enabled_default() -> bool:
|
|
251
|
+
"""Whether H-MEM phase reflection synthesizes summaries via the aux LLM (UP-09).
|
|
252
|
+
|
|
253
|
+
Default **ON** (mirrors the MEMORY_BACKEND default-on precedent). Disable with
|
|
254
|
+
REFLECTION_LLM_ENABLED in {none, off, false, 0, no, ''}.
|
|
255
|
+
|
|
256
|
+
SINGLE SOURCE OF TRUTH: both the model-provisioning site (construction.py) and the
|
|
257
|
+
runtime guard (TaskContextManager.__init__) MUST read this helper. The historical bug
|
|
258
|
+
(Fusion-validated 2026-06-16) was that the runtime guard read
|
|
259
|
+
`BotConfig.get("REFLECTION_LLM_ENABLED", False)` — and `BotConfig.get` is
|
|
260
|
+
`getattr(self, key, default)` with no such attribute, so it was ALWAYS False — while
|
|
261
|
+
construction.py read os.getenv. The two sources disagreed and reflection never fired.
|
|
262
|
+
"""
|
|
263
|
+
val = os.getenv("REFLECTION_LLM_ENABLED", "true").strip().lower()
|
|
264
|
+
return val not in ("none", "off", "false", "0", "no", "")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# --- Autonomy & continuous-learning loops (Reference-parity, 2026-06-16) ---------
|
|
268
|
+
#
|
|
269
|
+
# Shared flag helpers for the four loops POLYROB lacked: self-wake re-entry,
|
|
270
|
+
# writable-skills + background-review, cron run-loop+delivery, durable goal board,
|
|
271
|
+
# and the curator. SINGLE SOURCE OF TRUTH for the falsey-set semantics, mirroring
|
|
272
|
+
# reflection_llm_enabled_default() above. All loops default-OFF + fail-open except
|
|
273
|
+
# MEMORY_SEARCH_TOOL (read-only, tenant-scoped) and CRON_RUN_LOOP (fixes a live bug
|
|
274
|
+
# where cron built a session but never ran the agent loop).
|
|
275
|
+
|
|
276
|
+
_FALSEY = ("none", "off", "false", "0", "no", "")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _bool_env(name: str, default: bool) -> bool:
|
|
280
|
+
"""Read a boolean env var with POLYROB's falsey-set semantics.
|
|
281
|
+
|
|
282
|
+
Delegates to the repo-wide SSOT (``core.env.bool_env``) so this module shares
|
|
283
|
+
one parser with everything else instead of reimplementing it (the reflection-gate
|
|
284
|
+
bug was a parser/source mismatch). Kept as a thin wrapper so in-module callers
|
|
285
|
+
(and this module's public ``_bool_env`` symbol) are unaffected.
|
|
286
|
+
"""
|
|
287
|
+
return _core_bool_env(name, default)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _int_env(name: str, default: int) -> int:
|
|
291
|
+
try:
|
|
292
|
+
return int(os.getenv(name, str(default)))
|
|
293
|
+
except (TypeError, ValueError):
|
|
294
|
+
return default
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# --- Local (terminal-native, single-user) profile -------------------------
|
|
298
|
+
# When POLYROB_LOCAL is truthy, the *safe* autonomy/learning flags default ON as a
|
|
299
|
+
# group, so a terminal user gets the W1-W7 loops without setting ~6 env vars.
|
|
300
|
+
# Multi-tenant server entry (main.py / uvicorn) never sets POLYROB_LOCAL, so its
|
|
301
|
+
# defaults are unchanged. An explicit per-flag value (e.g. GOALS_ENABLED=off)
|
|
302
|
+
# still wins — only the *default* moves.
|
|
303
|
+
#
|
|
304
|
+
# Excludes anything with a multi-tenant blast radius even on one machine:
|
|
305
|
+
# CODE_EXEC_ENABLED (not a sandbox) and the sub-agent concurrency caps.
|
|
306
|
+
_SAFE_LOCAL_FLAGS = frozenset({
|
|
307
|
+
"SELF_WAKE_ENABLED",
|
|
308
|
+
"SKILLS_WRITABLE",
|
|
309
|
+
"SELF_CONTEXT_WRITABLE",
|
|
310
|
+
# Bounded owner-facts doc (USER.md-equivalent): safe on a single-user CLI
|
|
311
|
+
# (own tenant, quarantine-then-promote); multi-tenant server stays OFF.
|
|
312
|
+
"OWNER_DOC_WRITABLE",
|
|
313
|
+
"BACKGROUND_REVIEW_ENABLED",
|
|
314
|
+
"GOALS_ENABLED",
|
|
315
|
+
"CURATOR_ENABLED",
|
|
316
|
+
"INSIGHTS_TOOL",
|
|
317
|
+
"CODING_TOOLS_ENABLED",
|
|
318
|
+
# P0-D: structured git over the confined workspace. Safe on a single-user CLI
|
|
319
|
+
# (own repo); multi-tenant server stays OFF by default. git_push is separately
|
|
320
|
+
# approval-gated + leaf-blocked (Task 9).
|
|
321
|
+
"GIT_TOOLS_ENABLED",
|
|
322
|
+
# NOTE (FL-D9): SKILL_CATALOG_INCLUDE_ALL was here, but its resolver
|
|
323
|
+
# (skill_catalog_include_all(), below) hardcodes `_bool_env("SKILL_CATALOG_INCLUDE_ALL",
|
|
324
|
+
# True)` directly and never consults `_safe_autonomy_default`/this set — the entry
|
|
325
|
+
# was dead (default is already ON everywhere). Removed 2026-07 (behavior-neutral).
|
|
326
|
+
# KB (knowledge-base) feature: safe on a single-user CLI (read/write own KB),
|
|
327
|
+
# multi-tenant default stays OFF until per-tenant isolation is verified.
|
|
328
|
+
"KB_ENABLED",
|
|
329
|
+
# C1: context-reference expansion (@file/@folder/@diff/@url). Safe on a single-user
|
|
330
|
+
# CLI where the workspace is trusted; multi-tenant server stays OFF by default so
|
|
331
|
+
# accidental file-inclusion from a shared workspace is not the default.
|
|
332
|
+
"CONTEXT_REFERENCES_ENABLED",
|
|
333
|
+
# C9: auto-load CLAUDE.md/AGENTS.md/.cursorrules as a PROJECT_CONTEXT foundation
|
|
334
|
+
# message. Safe on a single-user CLI (reads from cwd/git-root); server stays OFF
|
|
335
|
+
# (multi-tenant workspaces may not have a project context file).
|
|
336
|
+
"PROJECT_CONTEXT_AUTOLOAD",
|
|
337
|
+
# T13: KB auto-prefetch — inject KB recall alongside memory recall at step start.
|
|
338
|
+
# Safe on a single-user CLI (reads own KB); multi-tenant server stays OFF by default
|
|
339
|
+
# because KB_ENABLED itself is also local-only by default.
|
|
340
|
+
"KB_AUTO_PREFETCH",
|
|
341
|
+
# Task 2/3/4: episodic activity ledger — durable per-run provenance rows +
|
|
342
|
+
# digest injection + continuity bridge. Safe on a single-user CLI (own
|
|
343
|
+
# tenant); multi-tenant server stays OFF by default.
|
|
344
|
+
"EPISODIC_MEMORY_ENABLED",
|
|
345
|
+
"EPISODIC_DIGEST_INJECT",
|
|
346
|
+
"CONTINUITY_BRIDGE_ENABLED",
|
|
347
|
+
# AU-F1.1: the goal-board dispatcher ticks under POLYROB_LOCAL (GOALS_ENABLED is
|
|
348
|
+
# in this set), but without the planner nothing ever proposes an objective's next
|
|
349
|
+
# goal -- the board sits idle even though the ticker runs (the "idle since Jul 1"
|
|
350
|
+
# incident). Safe on a single-user CLI (own tenant's own objectives); multi-tenant
|
|
351
|
+
# server stays OFF by default. Existing gates (GOALS_ENABLED, an active objective,
|
|
352
|
+
# a thin ready-queue, the planner cooldown) still apply regardless of this default.
|
|
353
|
+
"GOAL_PLANNER_ENABLED",
|
|
354
|
+
# §7.1: self-evolution transparency — proactively notify the owner of a pending
|
|
355
|
+
# identity/skill proposal + expose the approve/reject/list surface. Safe on a
|
|
356
|
+
# single-user CLI (own tenant); multi-tenant server stays OFF by default (an
|
|
357
|
+
# unsolicited push to a shared owner channel is opt-in there).
|
|
358
|
+
"SELF_EVOLUTION_TRANSPARENCY",
|
|
359
|
+
# Task 5: gated `message` action (owner/allowlist -> MessageRouter send). Safe
|
|
360
|
+
# on a single-user CLI (own tenant, own owner-bound targets); multi-tenant
|
|
361
|
+
# server stays OFF by default (arbitrary outbound send is opt-in there).
|
|
362
|
+
"MESSAGE_TOOL_ENABLED",
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def local_mode_enabled() -> bool:
|
|
367
|
+
"""True when running as the single-user terminal-native agent.
|
|
368
|
+
|
|
369
|
+
Canonical flag: ``POLYROB_LOCAL``. ``ROB_LOCAL`` is accepted as a deprecated
|
|
370
|
+
back-compat alias (older docs/scripts referenced it) — either being truthy
|
|
371
|
+
enables local mode, so a doc that still says ``ROB_LOCAL`` isn't a silent no-op.
|
|
372
|
+
"""
|
|
373
|
+
return _bool_env("POLYROB_LOCAL", False) or _bool_env("ROB_LOCAL", False)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def message_tool_enabled() -> bool:
|
|
377
|
+
"""Whether the gated `message` action (owner/allowlist -> MessageRouter) is
|
|
378
|
+
registered. Default OFF; ON under POLYROB_LOCAL (single-user CLI) via the
|
|
379
|
+
_SAFE_LOCAL_FLAGS group. An explicit MESSAGE_TOOL_ENABLED always wins.
|
|
380
|
+
"""
|
|
381
|
+
return _bool_env("MESSAGE_TOOL_ENABLED", _safe_autonomy_default("MESSAGE_TOOL_ENABLED"))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def task_personality_block_enabled() -> bool:
|
|
385
|
+
"""Whether the persona/<identity> block is injected into the task agent.
|
|
386
|
+
|
|
387
|
+
S1 (chat consolidation): injects the chat agent's character/personality into
|
|
388
|
+
the unified Task agent's <identity> block so chat-mode carries persona without
|
|
389
|
+
a separate ChatAgent. The persona TEXT is rendered from a Character via the
|
|
390
|
+
pure agents/personality/persona_render.render_persona_block; the task-agent
|
|
391
|
+
core only ever sees a str (it never imports the chat stack). This access-time
|
|
392
|
+
gate is the live seam — the module-level TASK_PERSONALITY_BLOCK constant that
|
|
393
|
+
used to exist was a dead decoy.
|
|
394
|
+
|
|
395
|
+
Access-time (not import-time) so it sees POLYROB_LOCAL set via
|
|
396
|
+
os.environ.setdefault in bootstrap. Defaults ON under local mode (single-user
|
|
397
|
+
CLI wants its persona; persona_block resolves to "" when OFF => byte-identical
|
|
398
|
+
system prompt), OFF on the multi-tenant server. An explicit
|
|
399
|
+
TASK_PERSONALITY_BLOCK env always wins.
|
|
400
|
+
"""
|
|
401
|
+
raw = os.getenv("TASK_PERSONALITY_BLOCK")
|
|
402
|
+
if raw is None or raw.strip() == "":
|
|
403
|
+
return local_mode_enabled()
|
|
404
|
+
return _bool_env("TASK_PERSONALITY_BLOCK", False)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def memory_prefetch_cadence(autonomous: bool = False) -> int:
|
|
408
|
+
"""Steps between automatic memory re-prefetch (Phase 1.3).
|
|
409
|
+
|
|
410
|
+
0 = prefetch on the FIRST step only (legacy, prod-safe). N>0 = ALSO prefetch
|
|
411
|
+
every N steps so a long task keeps re-recalling phase-relevant memory instead of
|
|
412
|
+
recalling once at step 1 and never again.
|
|
413
|
+
|
|
414
|
+
Resolved at ACCESS time (not import) so it sees POLYROB_LOCAL even though that is
|
|
415
|
+
set via os.environ.setdefault in bootstrap, which may run after this module is
|
|
416
|
+
first imported. Defaults to 3 under local mode AND for an autonomous session
|
|
417
|
+
(SA-06 — a server goal/cron run otherwise recalled once at step 1, where the
|
|
418
|
+
brain enrichment is dead, and never again); 0 for server chat. An explicit
|
|
419
|
+
``MEMORY_PREFETCH_CADENCE`` (incl. ``0``) always wins.
|
|
420
|
+
"""
|
|
421
|
+
return _int_env("MEMORY_PREFETCH_CADENCE",
|
|
422
|
+
3 if (local_mode_enabled() or autonomous) else 0)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def hmem_tail_placement() -> bool:
|
|
426
|
+
"""Whether in-session hierarchical memory is placed as a dynamic SUFFIX after the
|
|
427
|
+
conversation (Phase 0.1) instead of in the foundation ahead of it.
|
|
428
|
+
|
|
429
|
+
The H-MEM block changes every step; in the foundation prefix it invalidated the
|
|
430
|
+
prompt cache for everything after it (skills tail + all conversation) on every
|
|
431
|
+
step. As a tail suffix, the stable foundation + growing conversation form a
|
|
432
|
+
cacheable prefix and only the small H-MEM suffix is reprocessed.
|
|
433
|
+
|
|
434
|
+
Resolved at access time. Default ON everywhere (T1-09, 2026-07-06): it soaked
|
|
435
|
+
locally since 2026-06 with no regressions, while the OFF server default broke the
|
|
436
|
+
server prompt cache every step. Explicit ``HMEM_TAIL_PLACEMENT=false`` restores
|
|
437
|
+
the legacy foundation placement.
|
|
438
|
+
"""
|
|
439
|
+
return _bool_env("HMEM_TAIL_PLACEMENT", True)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def ticker_idle_backoff_enabled() -> bool:
|
|
443
|
+
"""Whether idle background tickers (cron, goal dispatch) back off their poll
|
|
444
|
+
interval when a tick finds no due work, instead of firing at a fixed cadence
|
|
445
|
+
forever.
|
|
446
|
+
|
|
447
|
+
A fixed 60s ticker costs nothing on a multi-tenant server with steady job
|
|
448
|
+
volume, but on a single-user local CLI (POLYROB_LOCAL) it is close to the
|
|
449
|
+
only thing keeping the process from ever going idle -- a real contributor
|
|
450
|
+
to laptop battery drain. Backoff only kicks in on demonstrably idle ticks
|
|
451
|
+
(nothing ran, nothing failed) and resets to the base interval the moment
|
|
452
|
+
work resumes, so responsiveness is unaffected once something is actually
|
|
453
|
+
happening.
|
|
454
|
+
|
|
455
|
+
Resolved at access time. Defaults ON under POLYROB_LOCAL, OFF on the
|
|
456
|
+
multi-tenant server (byte-identical fixed-cadence legacy) so a precisely
|
|
457
|
+
time-scheduled cron job on a shared server never slips. Explicit
|
|
458
|
+
``TICKER_IDLE_BACKOFF_ENABLED`` always wins.
|
|
459
|
+
"""
|
|
460
|
+
return _bool_env("TICKER_IDLE_BACKOFF_ENABLED", local_mode_enabled())
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def ticker_idle_backoff_max_multiplier() -> int:
|
|
464
|
+
"""Cap on how many multiples of a ticker's base interval an idle backoff may
|
|
465
|
+
reach (e.g. 5x a 60s base = 300s = 5 minutes worst-case staleness before a
|
|
466
|
+
newly-due job is noticed). Explicit ``TICKER_IDLE_BACKOFF_MAX_MULTIPLIER``
|
|
467
|
+
always wins; default 5.
|
|
468
|
+
"""
|
|
469
|
+
return _int_env("TICKER_IDLE_BACKOFF_MAX_MULTIPLIER", 5)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def embedder_needed() -> bool:
|
|
473
|
+
"""Whether this deployment actually needs the sentence-transformers embedder (torch).
|
|
474
|
+
|
|
475
|
+
SSOT for both the CLI (maybe_register_cli_embedder) and the server (initialize_modules):
|
|
476
|
+
only build the heavy embedder when KB is enabled, MEMORY_BACKEND=local_vector (hybrid
|
|
477
|
+
vector recall), or local mode. The default MEMORY_BACKEND=sqlite uses FTS5 keyword recall
|
|
478
|
+
and needs no embeddings. See docs/plans/2026-06-26-runtime-architecture-finalization-FUSION.md (P1-EMB).
|
|
479
|
+
"""
|
|
480
|
+
return (
|
|
481
|
+
AutonomyConfig.kb_enabled()
|
|
482
|
+
or os.getenv("MEMORY_BACKEND", "sqlite").lower() == "local_vector"
|
|
483
|
+
or local_mode_enabled()
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _safe_autonomy_default(flag_name: str) -> bool:
|
|
488
|
+
"""Default for a safe autonomy flag: ON under local mode, else OFF."""
|
|
489
|
+
return local_mode_enabled() if flag_name in _SAFE_LOCAL_FLAGS else False
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# --- W1-1: AUTONOMY_POSTURE — one coherent switch for the shipped-but-dark loops ----
|
|
493
|
+
#
|
|
494
|
+
# Five autonomy flags shipped wired but default-OFF in BOTH modes, each behind its own
|
|
495
|
+
# env var, so making an instance actually verify + report its autonomous work meant
|
|
496
|
+
# flipping five independent flags with no single lever (the "activation, not machinery"
|
|
497
|
+
# gap). AUTONOMY_POSTURE is a second axis (orthogonal to _SAFE_LOCAL_FLAGS) that moves
|
|
498
|
+
# the DEFAULTS of that group together. An explicit per-flag env ALWAYS wins (only the
|
|
499
|
+
# default moves), and the unset/`silent` posture is byte-identical to today.
|
|
500
|
+
#
|
|
501
|
+
# silent (default) — today's behavior: autonomy runs but is unverified + silent.
|
|
502
|
+
# owner-visible — the agent's autonomous work becomes VERIFIED + owner-visible:
|
|
503
|
+
# completion judge, blocker->owner escalation (+ ask), self-wake
|
|
504
|
+
# delivery, continuity bridge. Safe for single-user local; on a
|
|
505
|
+
# multi-tenant server it is an opt-in (unsolicited pushes / aux cost).
|
|
506
|
+
# full — owner-visible PLUS time-based initiative (cron ticker).
|
|
507
|
+
_POSTURE_OWNER_VISIBLE_FLAGS = frozenset({
|
|
508
|
+
"GOAL_COMPLETION_JUDGE",
|
|
509
|
+
"GOAL_BLOCKER_ESCALATION",
|
|
510
|
+
"GOAL_SELF_WAKE_ENABLED",
|
|
511
|
+
"AUTONOMOUS_CONTINUITY_BRIDGE",
|
|
512
|
+
# Continuity/learning trio: ON under the local profile, and an owner-visible
|
|
513
|
+
# posture also turns it on server-side (memory + verification, no
|
|
514
|
+
# unsolicited-cost initiative). Explicit env wins.
|
|
515
|
+
"EPISODIC_MEMORY_ENABLED",
|
|
516
|
+
"EPISODIC_DIGEST_INJECT",
|
|
517
|
+
"REFLECTION_ON_SESSION_CLOSE",
|
|
518
|
+
# Budget-aware dispatch: protective + owner-legible (raises an ask, holds spend).
|
|
519
|
+
"BUDGET_AWARE_AUTONOMY",
|
|
520
|
+
})
|
|
521
|
+
_POSTURE_FULL_FLAGS = _POSTURE_OWNER_VISIBLE_FLAGS | {"CRON_ENABLED", "WAKE_CHANGE_GATE"}
|
|
522
|
+
_AUTONOMY_POSTURES = ("silent", "owner-visible", "full")
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def autonomy_posture() -> str:
|
|
526
|
+
"""Resolved AUTONOMY_POSTURE (silent|owner-visible|full).
|
|
527
|
+
|
|
528
|
+
Default `silent` in BOTH modes (byte-identical to pre-W1-1). An unknown value
|
|
529
|
+
degrades to `silent` so a typo never silently activates autonomy. Access-time so
|
|
530
|
+
tests/bootstrap env changes are seen.
|
|
531
|
+
"""
|
|
532
|
+
raw = (os.getenv("AUTONOMY_POSTURE") or "").strip().lower()
|
|
533
|
+
return raw if raw in _AUTONOMY_POSTURES else "silent"
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _posture_autonomy_default(flag_name: str) -> bool:
|
|
537
|
+
"""Default for a posture-governed autonomy flag, given the resolved posture."""
|
|
538
|
+
posture = autonomy_posture()
|
|
539
|
+
if posture == "full":
|
|
540
|
+
return flag_name in _POSTURE_FULL_FLAGS
|
|
541
|
+
if posture == "owner-visible":
|
|
542
|
+
return flag_name in _POSTURE_OWNER_VISIBLE_FLAGS
|
|
543
|
+
return False # silent: today's defaults
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
# --- AGENT_COMPUTE_POSTURE — the compute-capability ladder (computer-use parity) ----
|
|
547
|
+
#
|
|
548
|
+
# A THIRD capability axis, orthogonal to POLYROB_LOCAL (single-user trust profile)
|
|
549
|
+
# and AUTONOMY_POSTURE (which autonomy loops run): how much host/compute capability
|
|
550
|
+
# the agent has.
|
|
551
|
+
#
|
|
552
|
+
# 0 confined (default) — today's docker sandbox, no persistent shell.
|
|
553
|
+
# 1 sandbox-dev — persistent networked sandbox + importable pip installs +
|
|
554
|
+
# `shell` scoped INTO the container + loopback port-publish.
|
|
555
|
+
# 2 self-maintain — posture 1 + the approval-gated `self_env` verbs.
|
|
556
|
+
# 3 host — Hermes-parity host access; requires POLYROB_LOCAL and a
|
|
557
|
+
# single-tenant box (refused on network-facing surfaces).
|
|
558
|
+
#
|
|
559
|
+
# SECURITY CONTRACT:
|
|
560
|
+
# - Default CLOSED: unset/garbage/out-of-range -> 0. Garbage NEVER rounds up —
|
|
561
|
+
# only the literal values 0|1|2|3 are accepted (a typo'd "9" must not grant the
|
|
562
|
+
# host tier).
|
|
563
|
+
# - FROZEN AT IMPORT: the value is snapshotted once at module import so a
|
|
564
|
+
# mid-process env mutation (e.g. a prompt-injected write that reached an
|
|
565
|
+
# env-mutating surface) can never raise the running posture. Operators set it
|
|
566
|
+
# in real process env (systemd EnvironmentFile / shell / dotenv loaded at
|
|
567
|
+
# process start, see main.py) — never at runtime.
|
|
568
|
+
# - The posture is the "may" side only; the existing correspondent-taint gate and
|
|
569
|
+
# delegation blocklist stay the "never" side. `compute_posture_allows` is the
|
|
570
|
+
# ONE predicate every posture-gated capability must call.
|
|
571
|
+
|
|
572
|
+
def _resolve_compute_posture(raw) -> int:
|
|
573
|
+
"""PURE: parse one env value -> posture int. Only literal 0|1|2|3 accepted;
|
|
574
|
+
anything else (unset/garbage/out-of-range) degrades CLOSED to 0."""
|
|
575
|
+
try:
|
|
576
|
+
val = int(str(raw).strip())
|
|
577
|
+
except (TypeError, ValueError):
|
|
578
|
+
return 0
|
|
579
|
+
return val if val in (0, 1, 2, 3) else 0
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
_COMPUTE_POSTURE_FROZEN = _resolve_compute_posture(os.getenv("AGENT_COMPUTE_POSTURE"))
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def compute_posture() -> int:
|
|
586
|
+
"""Resolved AGENT_COMPUTE_POSTURE (0-3), FROZEN at import (see block comment)."""
|
|
587
|
+
return _COMPUTE_POSTURE_FROZEN
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _refreeze_compute_posture_for_tests() -> int:
|
|
591
|
+
"""TEST-ONLY seam: re-snapshot the frozen posture from the current env.
|
|
592
|
+
|
|
593
|
+
Production code must never call this — the freeze is the security property.
|
|
594
|
+
"""
|
|
595
|
+
global _COMPUTE_POSTURE_FROZEN
|
|
596
|
+
_COMPUTE_POSTURE_FROZEN = _resolve_compute_posture(os.getenv("AGENT_COMPUTE_POSTURE"))
|
|
597
|
+
return _COMPUTE_POSTURE_FROZEN
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
# Turn kinds that mark a forged (non-user) re-entry — kept in sync lazily with
|
|
601
|
+
# agents.task.agent.core.self_wake.FORGED_TURN_KINDS (the SSOT, stdlib-only module);
|
|
602
|
+
# this literal fallback only serves if that import ever fails, and failing CLOSED
|
|
603
|
+
# here means "treat as forged", never "let it through".
|
|
604
|
+
_FORGED_TURN_KINDS_FALLBACK = ("self_wake", "delegation_result")
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def compute_posture_allows(execution_context, min_posture: int) -> bool:
|
|
608
|
+
"""THE single gate predicate for posture-gated compute capabilities.
|
|
609
|
+
|
|
610
|
+
True only when ALL hold:
|
|
611
|
+
(a) frozen ``compute_posture() >= min_posture``;
|
|
612
|
+
(b) the session tenant is the OWNER — ``is_owner_local_safe`` (principal
|
|
613
|
+
match always wins; the POLYROB_LOCAL bypass is honored ONLY for the
|
|
614
|
+
CLI's ``local`` operator tenant, so a forgeable network sender under
|
|
615
|
+
POLYROB_LOCAL=1 on a public surface is never auto-owned);
|
|
616
|
+
(c) not a leaf/sub-agent context;
|
|
617
|
+
(d) not a forged self-wake/delegation-result re-entry turn (the
|
|
618
|
+
``metadata["turn_kind"]`` stamp, SK-F10).
|
|
619
|
+
|
|
620
|
+
An autonomous goal/cron session of the owner tenant PASSES (no forged stamp;
|
|
621
|
+
role is orchestrator) — deliberate: WS-8 provisions those runs with the
|
|
622
|
+
compute toolset, and the correspondent-taint gate + delegation blocklist
|
|
623
|
+
remain the independent "never" side. ``min_posture <= 0`` is the
|
|
624
|
+
unconditional baseline (posture-0 capabilities keep their own gates).
|
|
625
|
+
Fail-CLOSED: any fault in resolution denies.
|
|
626
|
+
"""
|
|
627
|
+
if min_posture <= 0:
|
|
628
|
+
return True
|
|
629
|
+
try:
|
|
630
|
+
if compute_posture() < min_posture:
|
|
631
|
+
return False
|
|
632
|
+
if execution_context is None:
|
|
633
|
+
return False
|
|
634
|
+
if getattr(execution_context, "is_sub_agent", False):
|
|
635
|
+
return False
|
|
636
|
+
if getattr(execution_context, "role", "leaf") != "orchestrator":
|
|
637
|
+
return False
|
|
638
|
+
metadata = getattr(execution_context, "metadata", None) or {}
|
|
639
|
+
try:
|
|
640
|
+
from agents.task.agent.core.self_wake import FORGED_TURN_KINDS as _kinds
|
|
641
|
+
except Exception:
|
|
642
|
+
_kinds = _FORGED_TURN_KINDS_FALLBACK
|
|
643
|
+
if metadata.get("turn_kind") in _kinds:
|
|
644
|
+
return False
|
|
645
|
+
from core.instance import is_owner_local_safe, resolve_owner_principal
|
|
646
|
+
return is_owner_local_safe(
|
|
647
|
+
getattr(execution_context, "user_id", None),
|
|
648
|
+
owner_principal=resolve_owner_principal(),
|
|
649
|
+
local_enabled=local_mode_enabled(),
|
|
650
|
+
)
|
|
651
|
+
except Exception:
|
|
652
|
+
return False # fail-closed: can't prove entitlement -> deny
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
class AutonomyConfig:
|
|
656
|
+
"""Feature flags + caps for the autonomy/continuous-learning loops.
|
|
657
|
+
|
|
658
|
+
Read through this class (not raw os.getenv) so every loop shares one parser and
|
|
659
|
+
the defaults are documented in one place. Evaluated at access time (classmethod /
|
|
660
|
+
property-free) so tests can monkeypatch env between calls.
|
|
661
|
+
"""
|
|
662
|
+
|
|
663
|
+
# W1 — self-wake rail
|
|
664
|
+
@staticmethod
|
|
665
|
+
def self_wake_enabled() -> bool:
|
|
666
|
+
return _bool_env("SELF_WAKE_ENABLED", _safe_autonomy_default("SELF_WAKE_ENABLED"))
|
|
667
|
+
|
|
668
|
+
@staticmethod
|
|
669
|
+
def self_wake_max_reentries() -> int:
|
|
670
|
+
return _int_env("SELF_WAKE_MAX_REENTRIES", 3)
|
|
671
|
+
|
|
672
|
+
@staticmethod
|
|
673
|
+
def self_wake_idle_backoff_sec() -> float:
|
|
674
|
+
try:
|
|
675
|
+
return float(os.getenv("SELF_WAKE_IDLE_BACKOFF_SEC", "30"))
|
|
676
|
+
except (TypeError, ValueError):
|
|
677
|
+
return 30.0
|
|
678
|
+
|
|
679
|
+
# W2 — writable skills + background review
|
|
680
|
+
@staticmethod
|
|
681
|
+
def skills_writable() -> bool:
|
|
682
|
+
return _bool_env("SKILLS_WRITABLE", _safe_autonomy_default("SKILLS_WRITABLE"))
|
|
683
|
+
|
|
684
|
+
@staticmethod
|
|
685
|
+
def skills_writable_require_review() -> bool:
|
|
686
|
+
return _bool_env("SKILLS_WRITABLE_REQUIRE_REVIEW", True)
|
|
687
|
+
|
|
688
|
+
@staticmethod
|
|
689
|
+
def skill_overwrite_protect() -> bool:
|
|
690
|
+
# An agent/background overwrite of an existing ACTIVE skill becomes a .pending
|
|
691
|
+
# proposal (owner promotes); all overwrites archive the prior body. Default ON.
|
|
692
|
+
return _bool_env("SKILL_OVERWRITE_PROTECT", True)
|
|
693
|
+
|
|
694
|
+
# polyrob C-write — evolving SELF identity (agent-writable per-(instance,user) doc)
|
|
695
|
+
@staticmethod
|
|
696
|
+
def self_context_writable() -> bool:
|
|
697
|
+
return _bool_env("SELF_CONTEXT_WRITABLE", _safe_autonomy_default("SELF_CONTEXT_WRITABLE"))
|
|
698
|
+
|
|
699
|
+
@staticmethod
|
|
700
|
+
def self_context_require_review() -> bool:
|
|
701
|
+
return _bool_env("SELF_CONTEXT_REQUIRE_REVIEW", True)
|
|
702
|
+
|
|
703
|
+
# Bounded owner-facts doc (USER.md-equivalent) — agent-maintained per-(instance,
|
|
704
|
+
# user) document of durable owner facts/preferences, injected on the SELF/SOUL
|
|
705
|
+
# seam. Same quarantine-then-promote model as SELF; ON under the local profile.
|
|
706
|
+
@staticmethod
|
|
707
|
+
def owner_doc_writable() -> bool:
|
|
708
|
+
return _bool_env("OWNER_DOC_WRITABLE", _safe_autonomy_default("OWNER_DOC_WRITABLE"))
|
|
709
|
+
|
|
710
|
+
@staticmethod
|
|
711
|
+
def owner_doc_require_review() -> bool:
|
|
712
|
+
return _bool_env("OWNER_DOC_REQUIRE_REVIEW", True)
|
|
713
|
+
|
|
714
|
+
# §7.1 — self-evolution transparency + owner control loop
|
|
715
|
+
@staticmethod
|
|
716
|
+
def self_evolution_transparency() -> bool:
|
|
717
|
+
return _bool_env("SELF_EVOLUTION_TRANSPARENCY",
|
|
718
|
+
_safe_autonomy_default("SELF_EVOLUTION_TRANSPARENCY"))
|
|
719
|
+
|
|
720
|
+
@staticmethod
|
|
721
|
+
def background_review_enabled() -> bool:
|
|
722
|
+
return _bool_env("BACKGROUND_REVIEW_ENABLED", _safe_autonomy_default("BACKGROUND_REVIEW_ENABLED"))
|
|
723
|
+
|
|
724
|
+
@staticmethod
|
|
725
|
+
def bg_review_interval() -> int:
|
|
726
|
+
return _int_env("BG_REVIEW_INTERVAL", 10)
|
|
727
|
+
|
|
728
|
+
@staticmethod
|
|
729
|
+
def bg_review_max_steps() -> int:
|
|
730
|
+
return _int_env("BG_REVIEW_MAX_STEPS", 8)
|
|
731
|
+
|
|
732
|
+
# W3 — cron run-loop + delivery
|
|
733
|
+
@staticmethod
|
|
734
|
+
def cron_run_loop() -> bool:
|
|
735
|
+
return _bool_env("CRON_RUN_LOOP", True)
|
|
736
|
+
|
|
737
|
+
@staticmethod
|
|
738
|
+
def cron_delivery_enabled() -> bool:
|
|
739
|
+
return _bool_env("CRON_DELIVERY_ENABLED", False)
|
|
740
|
+
|
|
741
|
+
# Owner daily digest: a cron job carrying payload.digest is composed
|
|
742
|
+
# deterministically ($0, no model turn) from the ledger + event log + open
|
|
743
|
+
# asks and pushed via the cron delivery rail. Default OFF.
|
|
744
|
+
@staticmethod
|
|
745
|
+
def owner_digest_enabled() -> bool:
|
|
746
|
+
return _bool_env("OWNER_DIGEST_ENABLED", False)
|
|
747
|
+
|
|
748
|
+
# W4 — durable goal board
|
|
749
|
+
@staticmethod
|
|
750
|
+
def goals_enabled() -> bool:
|
|
751
|
+
return _bool_env("GOALS_ENABLED", _safe_autonomy_default("GOALS_ENABLED"))
|
|
752
|
+
|
|
753
|
+
@staticmethod
|
|
754
|
+
def goal_max_retries() -> int:
|
|
755
|
+
return _int_env("GOAL_MAX_RETRIES", 2)
|
|
756
|
+
|
|
757
|
+
@staticmethod
|
|
758
|
+
def goal_claim_ttl_sec() -> int:
|
|
759
|
+
return _int_env("GOAL_CLAIM_TTL_SEC", 900)
|
|
760
|
+
|
|
761
|
+
@staticmethod
|
|
762
|
+
def goal_max_run_seconds() -> int:
|
|
763
|
+
"""H11: hard wall-clock cap on a single goal run (mirrors cron's per-job cap).
|
|
764
|
+
A goal is otherwise bounded only by max_steps, so one hung step (tool/LLM/browser)
|
|
765
|
+
blocks forever and permanently occupies a GOAL_MAX_CONCURRENT slot."""
|
|
766
|
+
return _int_env("GOAL_MAX_RUN_SECONDS", 1800)
|
|
767
|
+
|
|
768
|
+
@staticmethod
|
|
769
|
+
def goal_dispatch_interval_sec() -> int:
|
|
770
|
+
return _int_env("GOAL_DISPATCH_INTERVAL_SEC", 60)
|
|
771
|
+
|
|
772
|
+
@staticmethod
|
|
773
|
+
def goal_max_concurrent() -> int:
|
|
774
|
+
return _int_env("GOAL_MAX_CONCURRENT", 2)
|
|
775
|
+
|
|
776
|
+
@staticmethod
|
|
777
|
+
def goal_dedup_threshold() -> float:
|
|
778
|
+
try:
|
|
779
|
+
return float(os.getenv("GOAL_DEDUP_THRESHOLD", "0.6"))
|
|
780
|
+
except (TypeError, ValueError):
|
|
781
|
+
return 0.6
|
|
782
|
+
|
|
783
|
+
@staticmethod
|
|
784
|
+
def goal_planner_enabled() -> bool:
|
|
785
|
+
return _bool_env("GOAL_PLANNER_ENABLED", _safe_autonomy_default("GOAL_PLANNER_ENABLED"))
|
|
786
|
+
|
|
787
|
+
@staticmethod
|
|
788
|
+
def goal_planner_min_ready() -> int:
|
|
789
|
+
return _int_env("GOAL_PLANNER_MIN_READY", 2)
|
|
790
|
+
|
|
791
|
+
@staticmethod
|
|
792
|
+
def goal_planner_cooldown_sec() -> int:
|
|
793
|
+
return _int_env("GOAL_PLANNER_COOLDOWN_SEC", 3600)
|
|
794
|
+
|
|
795
|
+
@staticmethod
|
|
796
|
+
def goal_planner_history_n() -> int:
|
|
797
|
+
return _int_env("GOAL_PLANNER_HISTORY_N", 10)
|
|
798
|
+
|
|
799
|
+
@staticmethod
|
|
800
|
+
def goal_daily_quota() -> int:
|
|
801
|
+
"""Max goal runs started per trailing 24h; <=0 disables the rail."""
|
|
802
|
+
return _int_env("GOAL_DAILY_QUOTA", 6)
|
|
803
|
+
|
|
804
|
+
@staticmethod
|
|
805
|
+
def goal_self_wake_enabled() -> bool:
|
|
806
|
+
# Was unconditional; redundant-cost finding (grok livetest 2026-06-27).
|
|
807
|
+
# W1-1: default governed by AUTONOMY_POSTURE (owner-visible/full turn it on).
|
|
808
|
+
return _bool_env("GOAL_SELF_WAKE_ENABLED",
|
|
809
|
+
_posture_autonomy_default("GOAL_SELF_WAKE_ENABLED"))
|
|
810
|
+
|
|
811
|
+
# Budget-aware dispatch: before claiming a goal, consult the unified ledger for
|
|
812
|
+
# the tenant's trailing spend; over budget -> raise an owner-visible ask and
|
|
813
|
+
# hold the goal instead of burning credits. Default governed by AUTONOMY_POSTURE
|
|
814
|
+
# (owner-visible/full) — a protective, owner-legible behavior. Explicit env wins.
|
|
815
|
+
@staticmethod
|
|
816
|
+
def budget_aware_autonomy() -> bool:
|
|
817
|
+
return _bool_env("BUDGET_AWARE_AUTONOMY",
|
|
818
|
+
_posture_autonomy_default("BUDGET_AWARE_AUTONOMY"))
|
|
819
|
+
|
|
820
|
+
@staticmethod
|
|
821
|
+
def autonomy_budget_usd() -> float:
|
|
822
|
+
"""Trailing-window spend ceiling for autonomous goal dispatch (USD).
|
|
823
|
+
<=0 disables the ceiling (never over budget)."""
|
|
824
|
+
try:
|
|
825
|
+
return float(os.getenv("AUTONOMY_BUDGET_USD", "10"))
|
|
826
|
+
except (TypeError, ValueError):
|
|
827
|
+
return 10.0
|
|
828
|
+
|
|
829
|
+
@staticmethod
|
|
830
|
+
def autonomy_budget_window_days() -> int:
|
|
831
|
+
return _int_env("AUTONOMY_BUDGET_WINDOW_DAYS", 1)
|
|
832
|
+
|
|
833
|
+
# §3.2 (goal-completion-verification, 2026-07-05) — judge a completed goal's
|
|
834
|
+
# acceptance with a cheap aux model. 'unmet' -> record_failure, 'unclear'/error/
|
|
835
|
+
# timeout -> pass (fail-open). W1-1: default governed by AUTONOMY_POSTURE.
|
|
836
|
+
@staticmethod
|
|
837
|
+
def goal_completion_judge() -> bool:
|
|
838
|
+
return _bool_env("GOAL_COMPLETION_JUDGE",
|
|
839
|
+
_posture_autonomy_default("GOAL_COMPLETION_JUDGE"))
|
|
840
|
+
|
|
841
|
+
@staticmethod
|
|
842
|
+
def goal_judge_timeout_sec() -> int:
|
|
843
|
+
return _int_env("GOAL_JUDGE_TIMEOUT_SEC", 60)
|
|
844
|
+
|
|
845
|
+
# Wake change-gate: a change-gated cron review
|
|
846
|
+
# tick skips the paid model call when the tenant's observable state hasn't
|
|
847
|
+
# moved since the last tick (cron/wake_gate.py). Posture `full` turns it on
|
|
848
|
+
# by default — it pairs with CRON_ENABLED; per-job opt-in via
|
|
849
|
+
# payload.change_gated, delivery jobs never gated.
|
|
850
|
+
@staticmethod
|
|
851
|
+
def wake_change_gate() -> bool:
|
|
852
|
+
return _bool_env("WAKE_CHANGE_GATE",
|
|
853
|
+
_posture_autonomy_default("WAKE_CHANGE_GATE"))
|
|
854
|
+
|
|
855
|
+
# §7.2 — blocker → owner escalation. When a goal trips the circuit breaker
|
|
856
|
+
# (status='blocked') OR the pipeline drains, surface a concrete ask to the owner
|
|
857
|
+
# instead of dying silently. Default OFF (an unsolicited owner push is opt-in).
|
|
858
|
+
@staticmethod
|
|
859
|
+
def goal_blocker_escalation() -> bool:
|
|
860
|
+
# W1-1: default governed by AUTONOMY_POSTURE (owner-visible/full turn it on).
|
|
861
|
+
return _bool_env("GOAL_BLOCKER_ESCALATION",
|
|
862
|
+
_posture_autonomy_default("GOAL_BLOCKER_ESCALATION"))
|
|
863
|
+
|
|
864
|
+
@staticmethod
|
|
865
|
+
def goal_empty_pipeline_escalate_after() -> int:
|
|
866
|
+
"""Consecutive planner runs that leave the ready queue EMPTY before the
|
|
867
|
+
stall escalates to the owner (rides GOAL_BLOCKER_ESCALATION)."""
|
|
868
|
+
return _int_env("GOAL_EMPTY_PIPELINE_ESCALATE_AFTER", 2)
|
|
869
|
+
|
|
870
|
+
# §7.5 — autonomous continuity bridge. Carry a recent-activity summary INTO a
|
|
871
|
+
# goal/cron tick (opposite scoping to the chat digest) so autonomous runs stop
|
|
872
|
+
# re-deriving "nothing new" every tick. Default OFF (additive context; verify
|
|
873
|
+
# token cost before flipping on).
|
|
874
|
+
@staticmethod
|
|
875
|
+
def autonomous_continuity_bridge() -> bool:
|
|
876
|
+
# W1-1: default governed by AUTONOMY_POSTURE (owner-visible/full turn it on).
|
|
877
|
+
return _bool_env("AUTONOMOUS_CONTINUITY_BRIDGE",
|
|
878
|
+
_posture_autonomy_default("AUTONOMOUS_CONTINUITY_BRIDGE"))
|
|
879
|
+
|
|
880
|
+
# W5 — curator
|
|
881
|
+
@staticmethod
|
|
882
|
+
def curator_enabled() -> bool:
|
|
883
|
+
return _bool_env("CURATOR_ENABLED", _safe_autonomy_default("CURATOR_ENABLED"))
|
|
884
|
+
|
|
885
|
+
@staticmethod
|
|
886
|
+
def curator_interval_hours() -> int:
|
|
887
|
+
return _int_env("CURATOR_INTERVAL_HOURS", 168)
|
|
888
|
+
|
|
889
|
+
@staticmethod
|
|
890
|
+
def curator_stale_days() -> int:
|
|
891
|
+
return _int_env("CURATOR_STALE_DAYS", 30)
|
|
892
|
+
|
|
893
|
+
@staticmethod
|
|
894
|
+
def curator_archive_days() -> int:
|
|
895
|
+
return _int_env("CURATOR_ARCHIVE_DAYS", 90)
|
|
896
|
+
|
|
897
|
+
# (curator_llm_merge / CURATOR_LLM_MERGE removed 2026-06-29 — the Phase-2 merge step
|
|
898
|
+
# it gated was a logged no-op with no merge policy. Re-add under its own flag when a
|
|
899
|
+
# concrete policy exists.)
|
|
900
|
+
|
|
901
|
+
# W6 — cross-session search tool (read-only, default-on)
|
|
902
|
+
@staticmethod
|
|
903
|
+
def memory_search_tool() -> bool:
|
|
904
|
+
return _bool_env("MEMORY_SEARCH_TOOL", True)
|
|
905
|
+
|
|
906
|
+
# W7 — insights tool (read-only authored-skill reuse metric)
|
|
907
|
+
@staticmethod
|
|
908
|
+
def insights_tool() -> bool:
|
|
909
|
+
return _bool_env("INSIGHTS_TOOL", _safe_autonomy_default("INSIGHTS_TOOL"))
|
|
910
|
+
|
|
911
|
+
# KB — knowledge-base feature gate (Task 2 / local_vector prerequisite)
|
|
912
|
+
@staticmethod
|
|
913
|
+
def kb_enabled() -> bool:
|
|
914
|
+
return _bool_env("KB_ENABLED", _safe_autonomy_default("KB_ENABLED"))
|
|
915
|
+
|
|
916
|
+
# C1 — context-reference expansion (@file/@folder/@diff/@url)
|
|
917
|
+
# Default ON under POLYROB_LOCAL (single-user CLI), OFF on the server.
|
|
918
|
+
@staticmethod
|
|
919
|
+
def context_references_enabled() -> bool:
|
|
920
|
+
return _bool_env(
|
|
921
|
+
"CONTEXT_REFERENCES_ENABLED",
|
|
922
|
+
_safe_autonomy_default("CONTEXT_REFERENCES_ENABLED"),
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
# C9 — auto-load CLAUDE.md/AGENTS.md/.cursorrules as a PROJECT_CONTEXT foundation
|
|
926
|
+
# message. Default ON under POLYROB_LOCAL (single-user CLI), OFF on the server.
|
|
927
|
+
@staticmethod
|
|
928
|
+
def project_context_autoload() -> bool:
|
|
929
|
+
return _bool_env(
|
|
930
|
+
"PROJECT_CONTEXT_AUTOLOAD",
|
|
931
|
+
_safe_autonomy_default("PROJECT_CONTEXT_AUTOLOAD"),
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
@staticmethod
|
|
935
|
+
def project_context_max_tokens() -> int:
|
|
936
|
+
return _int_env("PROJECT_CONTEXT_MAX_TOKENS", 20000)
|
|
937
|
+
|
|
938
|
+
# Phase 2 — server-side project-context opt-in. When ON (and NOT local mode),
|
|
939
|
+
# the loader runs on the server and the file is injected UNTRUSTED-WRAPPED
|
|
940
|
+
# (framed as DATA, not instructions). Default OFF and deliberately NOT a
|
|
941
|
+
# safe-local flag — POLYROB_LOCAL must not flip it on, so the multi-tenant
|
|
942
|
+
# server stays byte-identical unless an operator explicitly opts in.
|
|
943
|
+
@staticmethod
|
|
944
|
+
def project_context_server_mode() -> bool:
|
|
945
|
+
return _bool_env("PROJECT_CONTEXT_SERVER_MODE", False)
|
|
946
|
+
|
|
947
|
+
# T13 — KB auto-prefetch (inject KB recall alongside memory recall at step start)
|
|
948
|
+
# Default ON under POLYROB_LOCAL (single-user CLI), OFF on multi-tenant server.
|
|
949
|
+
@staticmethod
|
|
950
|
+
def kb_auto_prefetch() -> bool:
|
|
951
|
+
return _bool_env("KB_AUTO_PREFETCH", _safe_autonomy_default("KB_AUTO_PREFETCH"))
|
|
952
|
+
|
|
953
|
+
# Task 2 — episodic activity ledger (durable per-run provenance rows).
|
|
954
|
+
# Default ON under POLYROB_LOCAL (single-user CLI) OR AUTONOMY_POSTURE
|
|
955
|
+
# owner-visible/full (verified + owner-visible autonomy implies a durable
|
|
956
|
+
# activity ledger, so episodic is part of the posture group).
|
|
957
|
+
@staticmethod
|
|
958
|
+
def episodic_memory_enabled() -> bool:
|
|
959
|
+
return _bool_env("EPISODIC_MEMORY_ENABLED",
|
|
960
|
+
_safe_autonomy_default("EPISODIC_MEMORY_ENABLED")
|
|
961
|
+
or _posture_autonomy_default("EPISODIC_MEMORY_ENABLED"))
|
|
962
|
+
|
|
963
|
+
# Task 3 — inject a recent-episodes digest into the session.
|
|
964
|
+
@staticmethod
|
|
965
|
+
def episodic_digest_inject() -> bool:
|
|
966
|
+
return _bool_env("EPISODIC_DIGEST_INJECT",
|
|
967
|
+
_safe_autonomy_default("EPISODIC_DIGEST_INJECT")
|
|
968
|
+
or _posture_autonomy_default("EPISODIC_DIGEST_INJECT"))
|
|
969
|
+
|
|
970
|
+
# Session-close reflection (consolidate a short session's findings
|
|
971
|
+
# at close; one extra aux call per closed session). Posture-governed so an
|
|
972
|
+
# owner-visible instance actually learns from its autonomous runs. Consumer:
|
|
973
|
+
# modules/memory/task/task_context_manager.py (reads this resolver lazily).
|
|
974
|
+
@staticmethod
|
|
975
|
+
def reflection_on_session_close() -> bool:
|
|
976
|
+
return _bool_env("REFLECTION_ON_SESSION_CLOSE",
|
|
977
|
+
_posture_autonomy_default("REFLECTION_ON_SESSION_CLOSE"))
|
|
978
|
+
|
|
979
|
+
# Restart-durable autonomy state (background delegations + reentry
|
|
980
|
+
# budgets in autonomy_state.db). Default ON; off restores volatile registries.
|
|
981
|
+
@staticmethod
|
|
982
|
+
def autonomy_state_durable() -> bool:
|
|
983
|
+
return _bool_env("AUTONOMY_STATE_DURABLE", True)
|
|
984
|
+
|
|
985
|
+
# Task 4 — cross-session continuity bridge (thread_key stitching).
|
|
986
|
+
@staticmethod
|
|
987
|
+
def continuity_bridge_enabled() -> bool:
|
|
988
|
+
return _bool_env("CONTINUITY_BRIDGE_ENABLED", _safe_autonomy_default("CONTINUITY_BRIDGE_ENABLED"))
|
|
989
|
+
|
|
990
|
+
# Task 4 — LLM-generated continuity summary at reset. Intentionally NOT in
|
|
991
|
+
# _SAFE_LOCAL_FLAGS: OFF everywhere by default (adds latency at reset).
|
|
992
|
+
@staticmethod
|
|
993
|
+
def continuity_llm_summary() -> bool:
|
|
994
|
+
return _bool_env("CONTINUITY_LLM_SUMMARY", False) # OFF everywhere (latency at reset)
|
|
995
|
+
|
|
996
|
+
# Task 2 — episodic row retention window (days); pruning consumer TBD.
|
|
997
|
+
@staticmethod
|
|
998
|
+
def episodic_retention_days() -> int:
|
|
999
|
+
return _int_env("EPISODIC_RETENTION_DAYS", 90)
|
|
1000
|
+
|
|
1001
|
+
# T16 — interrupt-and-redirect: Ctrl-C mid-turn prompts for a redirect instruction
|
|
1002
|
+
# that becomes the next turn instead of silently aborting. Default OFF; NOT in
|
|
1003
|
+
# _SAFE_LOCAL_FLAGS (must be opt-in — changes SIGINT UX for all local users).
|
|
1004
|
+
@staticmethod
|
|
1005
|
+
def interrupt_redirect_enabled() -> bool:
|
|
1006
|
+
return _bool_env("INTERRUPT_REDIRECT", False)
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
class TimeoutConfig:
|
|
1010
|
+
"""Centralized timeout configuration - SINGLE SOURCE OF TRUTH.
|
|
1011
|
+
|
|
1012
|
+
All timeout values should be accessed through this class to ensure
|
|
1013
|
+
consistency across the codebase. Tool-specific timeouts account for
|
|
1014
|
+
the different latency characteristics of each tool type.
|
|
1015
|
+
|
|
1016
|
+
Sub-agent settings are loaded from BotConfig (core/config.py) which supports
|
|
1017
|
+
both config file and environment variable sources.
|
|
1018
|
+
"""
|
|
1019
|
+
|
|
1020
|
+
# Lazy-loaded config reference
|
|
1021
|
+
_config = None
|
|
1022
|
+
|
|
1023
|
+
@classmethod
|
|
1024
|
+
def _get_config(cls):
|
|
1025
|
+
"""Get BotConfig instance (lazy load to avoid circular imports)."""
|
|
1026
|
+
if cls._config is None:
|
|
1027
|
+
try:
|
|
1028
|
+
from core.config import BotConfig
|
|
1029
|
+
cls._config = BotConfig()
|
|
1030
|
+
except Exception:
|
|
1031
|
+
cls._config = None
|
|
1032
|
+
return cls._config
|
|
1033
|
+
|
|
1034
|
+
# ========== TOOL EXECUTION TIMEOUTS ==========
|
|
1035
|
+
# These are applied in controller/service.py multi_act()
|
|
1036
|
+
TOOL_TIMEOUTS = {
|
|
1037
|
+
'mcp': int(os.getenv('MCP_TIMEOUT_SECONDS', '180')), # MCP: network/subprocess latency
|
|
1038
|
+
'browser': int(os.getenv('BROWSER_TIMEOUT_SECONDS', '120')), # Browser: page loads, DOM operations
|
|
1039
|
+
'filesystem': int(os.getenv('FILESYSTEM_TIMEOUT_SECONDS', '30')), # Filesystem: fast local I/O
|
|
1040
|
+
'polymarket': int(os.getenv('POLYMARKET_TIMEOUT_SECONDS', '60')), # Polymarket API
|
|
1041
|
+
'default': int(os.getenv('DEFAULT_TOOL_TIMEOUT_SECONDS', '60')), # Default for unknown tools
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
# ========== SUB-AGENT CONTROLS ==========
|
|
1045
|
+
# Loaded from BotConfig (core/config.py) - supports config file + env vars
|
|
1046
|
+
# FIX (Jan 2026): Sub-agent system disabled by default - too chaotic
|
|
1047
|
+
# Use the getter methods for dynamic config-based values
|
|
1048
|
+
|
|
1049
|
+
@classmethod
|
|
1050
|
+
def get_sub_agents_enabled(cls) -> bool:
|
|
1051
|
+
"""Check if sub-agents are enabled (from BotConfig)."""
|
|
1052
|
+
config = cls._get_config()
|
|
1053
|
+
if config:
|
|
1054
|
+
return config.sub_agents_enabled
|
|
1055
|
+
return _core_bool_env('SUB_AGENTS_ENABLED', True)
|
|
1056
|
+
|
|
1057
|
+
@classmethod
|
|
1058
|
+
def get_subagent_least_privilege(cls) -> bool:
|
|
1059
|
+
"""UP-05: narrow a delegated child's toolset (drop code_execution/cronjob
|
|
1060
|
+
tool_ids + suppress delegation actions on leaf children via a dedicated
|
|
1061
|
+
child Controller). Default ON — the prior behaviour leaked the full parent
|
|
1062
|
+
toolset to children. Set SUBAGENT_LEAST_PRIVILEGE=false for the legacy
|
|
1063
|
+
shared-controller path (byte-identical to pre-UP-05)."""
|
|
1064
|
+
return _core_bool_env('SUBAGENT_LEAST_PRIVILEGE', True)
|
|
1065
|
+
|
|
1066
|
+
@classmethod
|
|
1067
|
+
def get_sub_agent_timeout(cls) -> int:
|
|
1068
|
+
"""Sub-agent timeout in seconds (from BotConfig)."""
|
|
1069
|
+
config = cls._get_config()
|
|
1070
|
+
if config:
|
|
1071
|
+
return config.sub_agent_timeout
|
|
1072
|
+
return int(os.getenv('SUB_AGENT_TIMEOUT_SECONDS', '600'))
|
|
1073
|
+
|
|
1074
|
+
@classmethod
|
|
1075
|
+
def get_parallel_subtasks_timeout(cls) -> int:
|
|
1076
|
+
"""Parallel subtasks timeout in seconds (from BotConfig)."""
|
|
1077
|
+
config = cls._get_config()
|
|
1078
|
+
if config:
|
|
1079
|
+
return config.parallel_subtasks_timeout
|
|
1080
|
+
return int(os.getenv('PARALLEL_SUBTASKS_TIMEOUT_SECONDS', '900'))
|
|
1081
|
+
|
|
1082
|
+
@classmethod
|
|
1083
|
+
def get_max_concurrent_sub_agents(cls) -> int:
|
|
1084
|
+
"""Maximum concurrent sub-agents (from BotConfig)."""
|
|
1085
|
+
config = cls._get_config()
|
|
1086
|
+
if config:
|
|
1087
|
+
return config.max_concurrent_sub_agents
|
|
1088
|
+
return int(os.getenv('MAX_CONCURRENT_SUB_AGENTS', '3'))
|
|
1089
|
+
|
|
1090
|
+
@classmethod
|
|
1091
|
+
def get_max_sub_agent_depth(cls) -> int:
|
|
1092
|
+
"""Maximum sub-agent nesting depth (from BotConfig)."""
|
|
1093
|
+
config = cls._get_config()
|
|
1094
|
+
if config:
|
|
1095
|
+
return config.max_sub_agent_depth
|
|
1096
|
+
return int(os.getenv('MAX_SUB_AGENT_DEPTH', '1'))
|
|
1097
|
+
|
|
1098
|
+
@classmethod
|
|
1099
|
+
def get_max_async_sub_agents(cls) -> int:
|
|
1100
|
+
"""Max concurrent BACKGROUND sub-agents (UP-12 delegate_task background=true).
|
|
1101
|
+
|
|
1102
|
+
Bounds how many background delegation slots may be live at once. Floors at 1
|
|
1103
|
+
and never exceeds get_max_concurrent_sub_agents() (background + sync share the
|
|
1104
|
+
same SubAgentManager semaphore, so the background cap must not exceed the total).
|
|
1105
|
+
Env: MAX_ASYNC_SUB_AGENTS (default 2).
|
|
1106
|
+
"""
|
|
1107
|
+
raw = int(os.getenv('MAX_ASYNC_SUB_AGENTS', '2'))
|
|
1108
|
+
ceiling = cls.get_max_concurrent_sub_agents()
|
|
1109
|
+
return max(1, min(raw, ceiling))
|
|
1110
|
+
|
|
1111
|
+
# Legacy attribute access - for backward compatibility
|
|
1112
|
+
# These call the getter methods
|
|
1113
|
+
SUB_AGENTS_ENABLED = True # C-DELEG: ON by default; use get_sub_agents_enabled() for dynamic value
|
|
1114
|
+
SUB_AGENT_TIMEOUT = 600
|
|
1115
|
+
PARALLEL_SUBTASKS_TIMEOUT = 900
|
|
1116
|
+
MAX_CONCURRENT_SUB_AGENTS = 3
|
|
1117
|
+
MAX_SUB_AGENT_DEPTH = 1
|
|
1118
|
+
|
|
1119
|
+
# ========== STEP/SESSION TIMEOUTS ==========
|
|
1120
|
+
STEP_TIMEOUT = float(os.getenv('STEP_TIMEOUT_SECONDS', '300')) # 5 min per step
|
|
1121
|
+
STALL_TIMEOUT = float(os.getenv('STALL_TIMEOUT_SECONDS', '300')) # 5 min stall detection
|
|
1122
|
+
|
|
1123
|
+
# ========== LLM REQUEST TIMEOUTS ==========
|
|
1124
|
+
LLM_REQUEST_TIMEOUT = int(os.getenv('LLM_REQUEST_TIMEOUT_SECONDS', '120')) # Standard LLM request
|
|
1125
|
+
LLM_STREAM_TIMEOUT = int(os.getenv('LLM_STREAM_TIMEOUT_SECONDS', '300')) # Streaming needs longer
|
|
1126
|
+
LLM_BASE_TIMEOUT = float(os.getenv('LLM_BASE_TIMEOUT_SECONDS', '30')) # Base (adjusted by tokens)
|
|
1127
|
+
|
|
1128
|
+
# ========== BROWSER CLEANUP TIMEOUTS ==========
|
|
1129
|
+
BROWSER_CLOSE = float(os.getenv('BROWSER_CLOSE_TIMEOUT', '3.0'))
|
|
1130
|
+
BROWSER_CONTEXT_CLOSE = float(os.getenv('BROWSER_CONTEXT_CLOSE_TIMEOUT', '8.0'))
|
|
1131
|
+
BROWSER_INSTANCE_CLOSE = float(os.getenv('BROWSER_INSTANCE_CLOSE_TIMEOUT', '5.0'))
|
|
1132
|
+
PROCESS_KILL = float(os.getenv('PROCESS_KILL_TIMEOUT', '2.0'))
|
|
1133
|
+
|
|
1134
|
+
@classmethod
|
|
1135
|
+
def get_tool_timeout(cls, tool_name: str) -> int:
|
|
1136
|
+
"""Get timeout for a specific tool.
|
|
1137
|
+
|
|
1138
|
+
Args:
|
|
1139
|
+
tool_name: Name of the tool (e.g., 'mcp', 'browser', 'filesystem')
|
|
1140
|
+
|
|
1141
|
+
Returns:
|
|
1142
|
+
Timeout in seconds for the tool
|
|
1143
|
+
"""
|
|
1144
|
+
if not tool_name:
|
|
1145
|
+
return cls.TOOL_TIMEOUTS['default']
|
|
1146
|
+
return cls.TOOL_TIMEOUTS.get(tool_name.lower(), cls.TOOL_TIMEOUTS['default'])
|
|
1147
|
+
|
|
1148
|
+
@classmethod
|
|
1149
|
+
def get_llm_timeout(cls, streaming: bool = False) -> int:
|
|
1150
|
+
"""Get timeout for LLM requests.
|
|
1151
|
+
|
|
1152
|
+
Args:
|
|
1153
|
+
streaming: Whether this is a streaming request
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
Timeout in seconds for the LLM request
|
|
1157
|
+
"""
|
|
1158
|
+
return cls.LLM_STREAM_TIMEOUT if streaming else cls.LLM_REQUEST_TIMEOUT
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
# Legacy constants for backward compatibility (deprecated - use TimeoutConfig instead)
|
|
1162
|
+
DEFAULT_STEP_TIMEOUT = TimeoutConfig.STEP_TIMEOUT
|
|
1163
|
+
DEFAULT_STALL_TIMEOUT = TimeoutConfig.STALL_TIMEOUT
|
|
1164
|
+
LLM_BASE_TIMEOUT = TimeoutConfig.LLM_BASE_TIMEOUT
|
|
1165
|
+
BROWSER_CLOSE_TIMEOUT = TimeoutConfig.BROWSER_CLOSE
|
|
1166
|
+
BROWSER_CONTEXT_CLOSE_TIMEOUT = TimeoutConfig.BROWSER_CONTEXT_CLOSE
|
|
1167
|
+
BROWSER_INSTANCE_CLOSE_TIMEOUT = TimeoutConfig.BROWSER_INSTANCE_CLOSE
|
|
1168
|
+
PROCESS_KILL_TIMEOUT = TimeoutConfig.PROCESS_KILL
|
|
1169
|
+
|
|
1170
|
+
# Agent Limits (defaults, can be overridden)
|
|
1171
|
+
DEFAULT_MAX_FAILURES = 5 # Maximum consecutive failures before stopping
|
|
1172
|
+
DEFAULT_MAX_ERROR_LENGTH = 400 # Maximum length for error messages
|
|
1173
|
+
DEFAULT_MAX_ACTIONS_PER_STEP = 10 # Maximum actions per step
|
|
1174
|
+
DEFAULT_MIN_INPUT_TOKENS = 1000 # Minimum safe input tokens
|
|
1175
|
+
|
|
1176
|
+
# MCP Tool Throttling (single source of truth)
|
|
1177
|
+
# MCP actions (scraping, searches, APIs) are expensive and execute SEQUENTIALLY
|
|
1178
|
+
# Each MCP action takes 30-180 seconds. Limiting prevents timeout cascades.
|
|
1179
|
+
MAX_MCP_PER_STEP = int(os.getenv('MAX_MCP_PER_STEP', '3')) # Configurable via env
|
|
1180
|
+
|
|
1181
|
+
# Context-compaction hysteresis (flow-efficiency D3-a)
|
|
1182
|
+
# LLM compaction (llm_compact_history) is an EXTRA LLM call. Without a cooldown it
|
|
1183
|
+
# re-fires every step once usage stays >=85% (a single large MCP result can re-cross
|
|
1184
|
+
# the line each step), doubling call cost on long runs. Enforce a minimum step gap
|
|
1185
|
+
# between LLM compactions; the >=95% emergency prune (non-LLM) remains the safety net.
|
|
1186
|
+
COMPACTION_COOLDOWN_STEPS = int(os.getenv('COMPACTION_COOLDOWN_STEPS', '3'))
|
|
1187
|
+
|
|
1188
|
+
# Compaction payload tuning (Reference-parity context upgrade, 2026-06).
|
|
1189
|
+
# These govern HOW the LLM compaction summarizes, not WHEN it fires (the tiered
|
|
1190
|
+
# thresholds in CompactionManager own the trigger). Previously the summary input
|
|
1191
|
+
# was silently truncated to the last 50 messages, each clipped to 500 chars, with
|
|
1192
|
+
# tool results dropped entirely -> the summary lost most of what it claimed to keep.
|
|
1193
|
+
COMPACTION_KEEP_RECENT = int(os.getenv('COMPACTION_KEEP_RECENT', '10')) # min recent msgs kept verbatim
|
|
1194
|
+
COMPACTION_TAIL_TOKEN_RATIO = float(os.getenv('COMPACTION_TAIL_TOKEN_RATIO', '0.20')) # C3: tail kept by token budget too
|
|
1195
|
+
COMPACTION_PER_MSG_CHARS = int(os.getenv('COMPACTION_PER_MSG_CHARS', '3000')) # A2: per-message head+tail budget into summarizer
|
|
1196
|
+
COMPACTION_TOOL_RESULT_CHARS = int(os.getenv('COMPACTION_TOOL_RESULT_CHARS', '2000')) # A2: tool-result budget (was: dropped)
|
|
1197
|
+
COMPACTION_MAX_SUMMARY_TOKENS = int(os.getenv('COMPACTION_MAX_SUMMARY_TOKENS', '12000')) # A3: ceiling for summary budget
|
|
1198
|
+
COMPACTION_MIN_SUMMARY_TOKENS = int(os.getenv('COMPACTION_MIN_SUMMARY_TOKENS', '2000')) # A3: floor for summary budget
|
|
1199
|
+
COMPACTION_MIN_SAVINGS_PCT = float(os.getenv('COMPACTION_MIN_SAVINGS_PCT', '10.0')) # B4: anti-thrash back-off threshold
|
|
1200
|
+
COMPACTION_CHECKPOINT = _core_bool_env('COMPACTION_CHECKPOINT', True) # C2: dump pre-compaction trajectory
|
|
1201
|
+
# A5: route the (expensive, repeated) summarization call to a cheaper auxiliary model.
|
|
1202
|
+
# Empty -> use the main model (backward-compatible). Format: "provider:model" or "model".
|
|
1203
|
+
COMPACTION_AUX_MODEL = os.getenv('COMPACTION_AUX_MODEL', '')
|
|
1204
|
+
|
|
1205
|
+
# Bounded tool-free reasoning turns (flow-efficiency D1-a, the seed resolution).
|
|
1206
|
+
# POLYROB forces >=1 function call every step. This allows a SMALL number of tool-free
|
|
1207
|
+
# "planning" turns to be treated as legitimate (gentle nudge) rather than a hard
|
|
1208
|
+
# error on the very first empty response. The 3-consecutive thinking-loop
|
|
1209
|
+
# escalation remains the hard backstop, so the loop bound is unchanged.
|
|
1210
|
+
ALLOWED_REASONING_TURNS = int(os.getenv('ALLOWED_REASONING_TURNS', '1'))
|
|
1211
|
+
|
|
1212
|
+
# S-1: progressive skill disclosure. When ON (the DEFAULT — see the function below),
|
|
1213
|
+
# only a compact <skill-catalog> (ids + one-line descriptions, ~0.15k) is injected and
|
|
1214
|
+
# the agent pulls a skill's full body on demand via the load_skill(skill_id) tool. When
|
|
1215
|
+
# OFF, skills are eager-injected full-body as a pinned user message (~3.2k tok).
|
|
1216
|
+
def skill_progressive_disclosure() -> bool:
|
|
1217
|
+
"""Access-time gate (mirrors skill_catalog_include_all). Default ON.
|
|
1218
|
+
|
|
1219
|
+
Falsey-set SSOT (`core.env.bool_env`): only none/off/false/0/no/'' disable.
|
|
1220
|
+
Access-time read (not an import-bound constant) so an env override always wins.
|
|
1221
|
+
"""
|
|
1222
|
+
return _core_bool_env('SKILL_PROGRESSIVE_DISCLOSURE', True)
|
|
1223
|
+
|
|
1224
|
+
# NOTE: the catalog-include-all policy is the access-time `skill_catalog_include_all()`
|
|
1225
|
+
# function below (defaults ON). A dead module-level constant that defaulted to 'false'
|
|
1226
|
+
# used to live here and was a decoy — no caller read it while the function returned True
|
|
1227
|
+
# (Phase 0.6: removed to end the "is it on or off?" confusion).
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
def skill_catalog_include_all() -> bool:
|
|
1231
|
+
"""Whether the <skill-catalog> lists ALL skills (not just trigger-matched).
|
|
1232
|
+
|
|
1233
|
+
P2-1a: defaults **ON everywhere** (model-chosen disclosure). The substring/regex
|
|
1234
|
+
trigger matcher is lossy — a paraphrased task can match zero skills, and on the
|
|
1235
|
+
server an unmatched skill is then undiscoverable (neither in the catalog nor
|
|
1236
|
+
load_skill-able). Listing the full compact catalog (id + one-line desc, bounded by
|
|
1237
|
+
``get_catalog_skills(max_skills)``, ~20-30 tok each, cache-stable) lets the agent
|
|
1238
|
+
discover and ``load_skill`` what it judges relevant. ~600 tok at the current library
|
|
1239
|
+
size. Set ``SKILL_CATALOG_INCLUDE_ALL=false`` to restore trigger-matched-only.
|
|
1240
|
+
Access-time read (not the import-bound constant) so an env override always wins.
|
|
1241
|
+
"""
|
|
1242
|
+
return _bool_env("SKILL_CATALOG_INCLUDE_ALL", True)
|
|
1243
|
+
|
|
1244
|
+
# UP-06: wrap untrusted tool-result content (mcp/browser/web/perplexity) in
|
|
1245
|
+
# <untrusted_tool_result> delimiters before it enters message history, + a <security>
|
|
1246
|
+
# system-prompt line teaching the model the wrapped content is DATA, not instructions.
|
|
1247
|
+
# Security control — default ON. OFF => byte-identical to legacy (no wrapper, no <security>).
|
|
1248
|
+
UNTRUSTED_TOOL_RESULT_WRAP = _core_bool_env('UNTRUSTED_TOOL_RESULT_WRAP', True)
|
|
1249
|
+
|
|
1250
|
+
# S1 (chat consolidation): the persona/<identity> injection gate lives in the
|
|
1251
|
+
# access-time `task_personality_block_enabled()` function above (there is no
|
|
1252
|
+
# module-level constant here — see that docstring for the full rationale).
|
|
1253
|
+
|
|
1254
|
+
# S2 (chat consolidation): tool-light "chat mode" for TaskAgent.chat_once. The
|
|
1255
|
+
# toolset must be NON-EMPTY — an empty tool_ids list trips the orchestrator's
|
|
1256
|
+
# comprehensive-default fallback (loads browser/mcp/etc). "task" is the harmless
|
|
1257
|
+
# TODO tool; chat mode otherwise relies on send_message/done + conversational-exit.
|
|
1258
|
+
CHAT_TOOL_IDS = [t.strip() for t in os.getenv('CHAT_TOOL_IDS', 'task').split(',') if t.strip()] or ['task']
|
|
1259
|
+
CHAT_MAX_STEPS = _int_env('CHAT_MAX_STEPS', 8)
|
|
1260
|
+
|
|
1261
|
+
# Chat consolidation (HANDOFF-C, 2026-06-19): the HTTP /api/chat/message endpoint
|
|
1262
|
+
# is now served SOLELY by the unified task agent (TaskAgent.chat_once); the legacy
|
|
1263
|
+
# ChatAgent was retired, so there is no toggle/fallback target anymore.
|
|
1264
|
+
#
|
|
1265
|
+
# The chat path does NO credit pre-check (matching the legacy behavior), so
|
|
1266
|
+
# chat_once skips create_session's pre-check by default (per-call billing still
|
|
1267
|
+
# applies). Set CHAT_SKIP_CREDIT_CHECK=off to apply the credit gate to chat.
|
|
1268
|
+
CHAT_SKIP_CREDIT_CHECK = os.getenv('CHAT_SKIP_CREDIT_CHECK', 'true').strip().lower() not in ('0', 'false', 'no', 'off', 'none', '')
|
|
1269
|
+
|
|
1270
|
+
# Human-in-the-Loop Defaults
|
|
1271
|
+
DEFAULT_RECENT_MESSAGES_LIMIT = 10 # Number of recent messages to fetch for context
|
|
1272
|
+
|
|
1273
|
+
# Screenshot and GIF Settings
|
|
1274
|
+
MAX_SCREENSHOTS_FOR_GIF = 20 # Maximum screenshots to include in GIF
|
|
1275
|
+
|
|
1276
|
+
# User Interaction Limits
|
|
1277
|
+
DEFAULT_MAX_USER_GUIDANCE_TOKENS = 1000 # Maximum tokens for user guidance messages
|
|
1278
|
+
DEFAULT_MAX_USER_MESSAGES_PER_STEP = 3 # Maximum user messages to process per step
|
|
1279
|
+
|
|
1280
|
+
# Session Management
|
|
1281
|
+
# NOTE (B3): the per-user session cap SSOT is BotConfig.max_sessions_per_user
|
|
1282
|
+
# (core/config.py, default 10), read at runtime in task_agent_lite. A duplicate
|
|
1283
|
+
# `MAX_SESSIONS_PER_USER = 100` used to live here but had ZERO call sites and only
|
|
1284
|
+
# misled readers — removed. Do not reintroduce a second cap constant here.
|
|
1285
|
+
SESSION_CLEANUP_DELAY = 2.0 # Delay before cleaning up completed sessions
|
|
1286
|
+
STALL_CHECK_INTERVAL = 30.0 # Check for stalls every 30 seconds
|
|
1287
|
+
|
|
1288
|
+
# Model-specific Tool Calling Instructions
|
|
1289
|
+
# Some models (Grok, certain OpenRouter models) struggle with nested argument placement
|
|
1290
|
+
# These instructions are injected into the system prompt for affected models
|
|
1291
|
+
GROK_TOOL_CALL_INSTRUCTIONS = """
|
|
1292
|
+
## 🚨 CRITICAL: MCP TOOL CALL FORMAT 🚨
|
|
1293
|
+
|
|
1294
|
+
**Do NOT send empty parameters — a tool with no params does nothing and fails.**
|
|
1295
|
+
|
|
1296
|
+
MCP tools are registered as DIRECT actions with FLAT parameters (there is no
|
|
1297
|
+
nested `arguments={...}` wrapper). Call the tool by its `{server}_{tool}` name
|
|
1298
|
+
and pass its parameters directly:
|
|
1299
|
+
|
|
1300
|
+
### ✅ CORRECT (direct, flat params):
|
|
1301
|
+
```python
|
|
1302
|
+
anysite_api(endpoint="/api/linkedin/search", params={"keywords": "CEO founder", "count": 10})
|
|
1303
|
+
webparser_parse(url="https://example.com")
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
### ❌ WRONG (the deprecated nested form — DO NOT use):
|
|
1307
|
+
```python
|
|
1308
|
+
mcp_execute_tool(server_name="anysite", tool_name="search", arguments={})
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
### THE RULE:
|
|
1312
|
+
- Call the tool DIRECTLY by its `{server}_{tool}` name; pass params inline/flat.
|
|
1313
|
+
- NEVER pass empty params — if you don't know what a tool needs, discover it
|
|
1314
|
+
first (e.g. `mcp_list_tools`) before calling.
|
|
1315
|
+
"""
|
|
1316
|
+
|
|
1317
|
+
# Models that need explicit tool argument instructions
|
|
1318
|
+
MODELS_NEEDING_TOOL_INSTRUCTIONS = [
|
|
1319
|
+
'grok',
|
|
1320
|
+
'x-ai',
|
|
1321
|
+
'xai',
|
|
1322
|
+
]
|
|
1323
|
+
|
|
1324
|
+
# ---------------------------------------------------------------------------
|
|
1325
|
+
# P-1: per-model-family operational guidance.
|
|
1326
|
+
# Short, high-signal nudges appended to the system prompt for non-Anthropic
|
|
1327
|
+
# families whose tool-calling/agent behavior benefits from explicit framing.
|
|
1328
|
+
# Kept terse so the cached system prefix stays small. Anthropic/Claude needs
|
|
1329
|
+
# none (the prompt is authored for it); Grok keeps its dedicated argument-format
|
|
1330
|
+
# block (MODELS_NEEDING_TOOL_INSTRUCTIONS) above and is not duplicated here.
|
|
1331
|
+
# ---------------------------------------------------------------------------
|
|
1332
|
+
GEMINI_FAMILY_INSTRUCTIONS = """## MODEL NOTE (Gemini)
|
|
1333
|
+
- Call exactly one function per step unless several are truly independent; after an
|
|
1334
|
+
optional brief planning turn, include at least one function call each step.
|
|
1335
|
+
- Use the exact tool names from the schema; do not invent wrapper names.
|
|
1336
|
+
- To reply to the user and finish, call done(text=...). A single non-blocking
|
|
1337
|
+
send_message does not end your turn; if you only reply without acting for a couple of
|
|
1338
|
+
steps the runtime ends the turn for you.
|
|
1339
|
+
"""
|
|
1340
|
+
|
|
1341
|
+
OPENAI_FAMILY_INSTRUCTIONS = """## MODEL NOTE (GPT)
|
|
1342
|
+
- Emit native function calls, not JSON-in-text. Every step must include ≥1 function call.
|
|
1343
|
+
- Prefer one decisive action per step over long deliberation.
|
|
1344
|
+
- To reply and finish in one shot, use done(text=...); reserve non-blocking
|
|
1345
|
+
send_message for a status update you immediately follow with more tool calls.
|
|
1346
|
+
"""
|
|
1347
|
+
|
|
1348
|
+
KIMI_FAMILY_INSTRUCTIONS = """## MODEL NOTE (Kimi)
|
|
1349
|
+
- Return tool calls as structured function calls only — never write tool-call
|
|
1350
|
+
delimiter tokens (e.g. <|tool_call_begin|>) into your text content.
|
|
1351
|
+
- One function call per step is enough; don't repeat the same message across steps.
|
|
1352
|
+
- To greet/answer and finish, call done(text=...). A single non-blocking send_message
|
|
1353
|
+
does not end your turn; if you only reply without acting for a couple of steps the
|
|
1354
|
+
runtime ends the turn for you, so prefer done() to finish deliberately.
|
|
1355
|
+
"""
|
|
1356
|
+
|
|
1357
|
+
# Substring → guidance. First match on the lowercased model name wins.
|
|
1358
|
+
MODEL_FAMILY_INSTRUCTIONS = [
|
|
1359
|
+
('kimi', KIMI_FAMILY_INSTRUCTIONS),
|
|
1360
|
+
('gemini', GEMINI_FAMILY_INSTRUCTIONS),
|
|
1361
|
+
('gpt', OPENAI_FAMILY_INSTRUCTIONS),
|
|
1362
|
+
]
|
|
1363
|
+
|
|
1364
|
+
# T1-10: families that reliably emit the brain-state JSON without a per-step
|
|
1365
|
+
# reminder. The prompt is authored for Claude (MODEL_FAMILY_INSTRUCTIONS gives it
|
|
1366
|
+
# no family note), so the INJECT_FORMAT_HINT_EARLY native-mode nag is pure noise
|
|
1367
|
+
# there — one wasted uncached message per step. Same needle mechanism as the
|
|
1368
|
+
# family notes: matched on the model name (plus the resolved provider).
|
|
1369
|
+
FORMAT_NAG_EXEMPT_FAMILIES = ('claude',)
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
def format_nag_exempt(model_name: str, provider: str = "") -> bool:
|
|
1373
|
+
"""Whether the per-step brain-state format reminder should be skipped."""
|
|
1374
|
+
name = (model_name or "").lower()
|
|
1375
|
+
if any(needle in name for needle in FORMAT_NAG_EXEMPT_FAMILIES):
|
|
1376
|
+
return True
|
|
1377
|
+
return (provider or "").lower() == "anthropic"
|
|
1378
|
+
|
|
1379
|
+
# OpenRouter models that may need extra help
|
|
1380
|
+
OPENROUTER_MODELS_NEEDING_HELP = [
|
|
1381
|
+
'grok',
|
|
1382
|
+
'mistral',
|
|
1383
|
+
'mixtral',
|
|
1384
|
+
]
|