polyrob 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/README.md +453 -0
- agents/__init__.py +164 -0
- agents/base_agent.py +743 -0
- agents/personality/character.py +119 -0
- agents/personality/character_manager.py +309 -0
- agents/personality/characters/rob.character.json +56 -0
- agents/personality/characters/trump.character.json +63 -0
- agents/personality/persona_render.py +78 -0
- agents/personality/persona_resolver.py +115 -0
- agents/prompt/__init__.py +58 -0
- agents/prompt/base_prompt.py +92 -0
- agents/prompt/system.py +417 -0
- agents/task/README_PATH_MANAGEMENT.md +141 -0
- agents/task/__init__.py +162 -0
- agents/task/agent/__init__.py +70 -0
- agents/task/agent/agent_state.py +408 -0
- agents/task/agent/async_delegation.py +274 -0
- agents/task/agent/autonomy_state.py +275 -0
- agents/task/agent/conversation.py +75 -0
- agents/task/agent/core/__init__.py +6 -0
- agents/task/agent/core/aux_metering.py +61 -0
- agents/task/agent/core/background_review.py +157 -0
- agents/task/agent/core/construction.py +1179 -0
- agents/task/agent/core/conversational_exit.py +49 -0
- agents/task/agent/core/correspondent_gate.py +227 -0
- agents/task/agent/core/curator.py +246 -0
- agents/task/agent/core/episodic_digest.py +103 -0
- agents/task/agent/core/error_recovery.py +480 -0
- agents/task/agent/core/history_io.py +257 -0
- agents/task/agent/core/llm_provisioning.py +287 -0
- agents/task/agent/core/llm_runner.py +547 -0
- agents/task/agent/core/logging_io.py +276 -0
- agents/task/agent/core/loop_detection.py +262 -0
- agents/task/agent/core/memory_prefetch.py +325 -0
- agents/task/agent/core/memory_writer.py +667 -0
- agents/task/agent/core/model_introspection.py +82 -0
- agents/task/agent/core/model_swap.py +177 -0
- agents/task/agent/core/next_action_internal.py +1331 -0
- agents/task/agent/core/output_validation.py +303 -0
- agents/task/agent/core/project_context.py +272 -0
- agents/task/agent/core/resources.py +93 -0
- agents/task/agent/core/result_processing.py +389 -0
- agents/task/agent/core/run_loop.py +661 -0
- agents/task/agent/core/safety_lifecycle.py +228 -0
- agents/task/agent/core/secret_guard.py +293 -0
- agents/task/agent/core/self_wake.py +280 -0
- agents/task/agent/core/session_metadata.py +104 -0
- agents/task/agent/core/step.py +781 -0
- agents/task/agent/core/step_execution.py +398 -0
- agents/task/agent/core/step_telemetry.py +222 -0
- agents/task/agent/core/test_aux_metering.py +50 -0
- agents/task/agent/core/test_billing_failover.py +68 -0
- agents/task/agent/core/test_fatal_error_classifier.py +38 -0
- agents/task/agent/core/test_judge_validation.py +275 -0
- agents/task/agent/core/test_model_swap.py +190 -0
- agents/task/agent/core/test_reflection_offload.py +54 -0
- agents/task/agent/core/turn_input.py +8 -0
- agents/task/agent/core/untrusted_wrap.py +103 -0
- agents/task/agent/core/user_ingress.py +228 -0
- agents/task/agent/hitl_manager.py +396 -0
- agents/task/agent/log_sanitize.py +36 -0
- agents/task/agent/message_manager/config.py +97 -0
- agents/task/agent/message_manager/service.py +758 -0
- agents/task/agent/message_manager/tests.py +305 -0
- agents/task/agent/message_manager/tool_call_builder.py +539 -0
- agents/task/agent/message_manager/tool_message_repair.py +364 -0
- agents/task/agent/message_manager/views.py +143 -0
- agents/task/agent/messages/__init__.py +8 -0
- agents/task/agent/messages/builders.py +309 -0
- agents/task/agent/messages/compactor.py +611 -0
- agents/task/agent/messages/context_references.py +271 -0
- agents/task/agent/messages/filters.py +333 -0
- agents/task/agent/messages/guidance.py +328 -0
- agents/task/agent/messages/persistence.py +448 -0
- agents/task/agent/messages/retrieval.py +506 -0
- agents/task/agent/messages/sqlite_persistence.py +85 -0
- agents/task/agent/messages/test_compaction_upgrade.py +349 -0
- agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
- agents/task/agent/messages/test_sqlite_persistence.py +54 -0
- agents/task/agent/messages/token_counter.py +532 -0
- agents/task/agent/orchestrator.py +776 -0
- agents/task/agent/profile_manager.py +105 -0
- agents/task/agent/profile_registry.py +173 -0
- agents/task/agent/prompts.py +1090 -0
- agents/task/agent/scenario_registry.py +226 -0
- agents/task/agent/service.py +509 -0
- agents/task/agent/session.py +984 -0
- agents/task/agent/skill_discovery.py +110 -0
- agents/task/agent/skill_frontmatter.py +94 -0
- agents/task/agent/skill_manager.py +1201 -0
- agents/task/agent/skill_store.py +627 -0
- agents/task/agent/skill_validation.py +56 -0
- agents/task/agent/skill_writer.py +492 -0
- agents/task/agent/sub_agent_manager.py +1137 -0
- agents/task/agent/test_session_disk_recovery.py +34 -0
- agents/task/agent/tests.py +513 -0
- agents/task/agent/tool_call_tracker.py +511 -0
- agents/task/agent/views.py +469 -0
- agents/task/config.py +432 -0
- agents/task/constants.py +1384 -0
- agents/task/flag_defaults.py +63 -0
- agents/task/goals/__init__.py +1 -0
- agents/task/goals/autonomy_marker.py +28 -0
- agents/task/goals/board.py +690 -0
- agents/task/goals/completion_judge.py +247 -0
- agents/task/goals/context.py +62 -0
- agents/task/goals/dispatcher.py +848 -0
- agents/task/goals/escalation.py +102 -0
- agents/task/goals/planner.py +114 -0
- agents/task/logging_config.py +155 -0
- agents/task/path.py +991 -0
- agents/task/robust_parse_config.py +400 -0
- agents/task/runtime/__init__.py +0 -0
- agents/task/runtime/run_as_session.py +130 -0
- agents/task/runtime_safety.py +308 -0
- agents/task/session/__init__.py +5 -0
- agents/task/session/browser_pool.py +130 -0
- agents/task/session/cleanup.py +612 -0
- agents/task/session/execution.py +397 -0
- agents/task/session/feed.py +200 -0
- agents/task/session/hitl_ingress.py +201 -0
- agents/task/session/hooks.py +83 -0
- agents/task/session/multi_agent.py +243 -0
- agents/task/session/workspace.py +91 -0
- agents/task/session_registry.py +91 -0
- agents/task/session_route.py +35 -0
- agents/task/sqlite_session_registry.py +261 -0
- agents/task/surface_config.py +185 -0
- agents/task/telemetry/__init__.py +58 -0
- agents/task/telemetry/event_log.py +180 -0
- agents/task/telemetry/formatters.py +642 -0
- agents/task/telemetry/manager.py +571 -0
- agents/task/telemetry/memory_events.py +66 -0
- agents/task/telemetry/self_events.py +53 -0
- agents/task/telemetry/sequence.py +160 -0
- agents/task/telemetry/service.py +1265 -0
- agents/task/telemetry/views.py +882 -0
- agents/task/templates.py +98 -0
- agents/task/test_aux_chain.py +151 -0
- agents/task/test_aux_router.py +41 -0
- agents/task/tests/test_parse_robustness.py +170 -0
- agents/task/tool_defaults.py +110 -0
- agents/task/utils.py +762 -0
- agents/task/utils_json.py +768 -0
- agents/task/utils_webview.py +86 -0
- agents/task/workspace_context.py +229 -0
- agents/task_agent_lite.py +2398 -0
- api/README.md +683 -0
- api/TASK_API_DOCS.md +151 -0
- api/__init__.py +0 -0
- api/a2a/__init__.py +37 -0
- api/a2a/agent_card.py +337 -0
- api/a2a/client.py +657 -0
- api/a2a/endpoints.py +375 -0
- api/a2a/models.py +268 -0
- api/a2a/streaming.py +419 -0
- api/a2a/task_handler.py +929 -0
- api/admin_endpoints.py +1584 -0
- api/app.py +923 -0
- api/auth_constants.py +28 -0
- api/auth_endpoints.py +402 -0
- api/auth_state.py +36 -0
- api/chat_via_task.py +42 -0
- api/conversation_manager.py +231 -0
- api/dependencies.py +186 -0
- api/eip8004_endpoints.py +452 -0
- api/hyperliquid_models.py +174 -0
- api/hyperliquid_routes.py +450 -0
- api/interfaces.py +207 -0
- api/jwt_middleware.py +83 -0
- api/kb/__init__.py +1 -0
- api/kb/endpoints.py +314 -0
- api/mcp_models.py +279 -0
- api/mcp_routes.py +438 -0
- api/middleware.py +526 -0
- api/models.py +176 -0
- api/openai_compat/__init__.py +0 -0
- api/openai_compat/model_map.py +25 -0
- api/openai_compat/models.py +59 -0
- api/openai_compat/router.py +140 -0
- api/payment_endpoints.py +444 -0
- api/payment_verification.py +138 -0
- api/polymarket_models.py +155 -0
- api/polymarket_routes.py +501 -0
- api/pricing_endpoints.py +89 -0
- api/session_routing.py +54 -0
- api/skill_endpoints.py +453 -0
- api/task_http_api.py +1856 -0
- api/webhooks.py +52 -0
- api/x402_endpoints.py +280 -0
- avatar/README.md +44 -0
- avatar/__init__.py +1 -0
- avatar/config/rob.json +7 -0
- avatar/mindprint.js +395 -0
- avatar/renders/rob.meta.json +20 -0
- avatar/renders/rob.png +0 -0
- avatar/studio.html +373 -0
- avatar/webview/avatar-live.js +44 -0
- cli/README.md +89 -0
- cli/__init__.py +0 -0
- cli/cli_surface.py +67 -0
- cli/commands/__init__.py +0 -0
- cli/commands/_bootstrap.py +49 -0
- cli/commands/_errors.py +53 -0
- cli/commands/chat.py +973 -0
- cli/commands/config.py +100 -0
- cli/commands/dashboard.py +103 -0
- cli/commands/doctor.py +222 -0
- cli/commands/email.py +174 -0
- cli/commands/gateway.py +346 -0
- cli/commands/goals.py +509 -0
- cli/commands/init.py +276 -0
- cli/commands/journey.py +30 -0
- cli/commands/kb.py +280 -0
- cli/commands/model.py +107 -0
- cli/commands/owner.py +431 -0
- cli/commands/pfp.py +232 -0
- cli/commands/run.py +414 -0
- cli/commands/serve.py +41 -0
- cli/commands/session.py +684 -0
- cli/commands/skill_install.py +786 -0
- cli/commands/skills.py +128 -0
- cli/commands/subagents.py +100 -0
- cli/commands/surface.py +81 -0
- cli/commands/telegram.py +185 -0
- cli/commands/todos.py +209 -0
- cli/commands/tools.py +102 -0
- cli/commands/update.py +316 -0
- cli/commands/whatsapp.py +175 -0
- cli/config_store.py +276 -0
- cli/gitignore.py +43 -0
- cli/inventory.py +46 -0
- cli/keys.py +101 -0
- cli/persona.py +17 -0
- cli/polyrob.py +137 -0
- cli/ui/__init__.py +64 -0
- cli/ui/activity.py +141 -0
- cli/ui/app.py +538 -0
- cli/ui/banner.py +233 -0
- cli/ui/blocks.py +534 -0
- cli/ui/bootstrap_notice.py +47 -0
- cli/ui/commands/__init__.py +41 -0
- cli/ui/commands/h_cron.py +64 -0
- cli/ui/commands/h_journey.py +164 -0
- cli/ui/commands/h_kb.py +121 -0
- cli/ui/commands/h_learn.py +100 -0
- cli/ui/commands/h_mcp.py +215 -0
- cli/ui/commands/h_self.py +113 -0
- cli/ui/commands/h_skills.py +314 -0
- cli/ui/commands/handlers.py +1672 -0
- cli/ui/commands/registry.py +426 -0
- cli/ui/dialog.py +425 -0
- cli/ui/event_registry.py +110 -0
- cli/ui/event_specs.py +45 -0
- cli/ui/events.py +433 -0
- cli/ui/identity.py +15 -0
- cli/ui/lifecycle.py +159 -0
- cli/ui/live_hooks.py +43 -0
- cli/ui/model_selector.py +532 -0
- cli/ui/persistent_loop.py +181 -0
- cli/ui/pick.py +113 -0
- cli/ui/plain_renderer.py +351 -0
- cli/ui/renderer.py +384 -0
- cli/ui/rich_renderer.py +533 -0
- cli/ui/secrets.py +115 -0
- cli/ui/state.py +376 -0
- cli/ui/statusbar.py +231 -0
- cli/ui/streaming.py +254 -0
- cli/ui/terminal_render.py +105 -0
- cli/ui/theme.py +132 -0
- cli/update/__init__.py +6 -0
- cli/update/context.py +121 -0
- cli/update/detect.py +176 -0
- cli/update/engine.py +95 -0
- cli/update/migrate_guarded.py +55 -0
- cli/update/process_guard.py +215 -0
- cli/update/runners.py +78 -0
- cli/update/snapshot.py +270 -0
- cli/update/versions.py +205 -0
- core/README.md +377 -0
- core/__init__.py +174 -0
- core/assets.py +87 -0
- core/async_bridge.py +76 -0
- core/autonomy_runtime.py +259 -0
- core/base_component.py +302 -0
- core/bootstrap.py +681 -0
- core/bot.py +310 -0
- core/config.py +833 -0
- core/constants.py +126 -0
- core/container.py +462 -0
- core/db_manifest.py +101 -0
- core/embedding.py +57 -0
- core/env.py +39 -0
- core/exceptions.py +287 -0
- core/flags.py +144 -0
- core/flags_catalog.py +345 -0
- core/home_migration.py +62 -0
- core/identity.py +118 -0
- core/initialization.py +1004 -0
- core/instance.py +537 -0
- core/interactive_gate.py +96 -0
- core/logging.py +436 -0
- core/owner_doc_writer.py +176 -0
- core/pairing.py +193 -0
- core/path_safety.py +20 -0
- core/paths.py +27 -0
- core/payment_config.py +21 -0
- core/permissions.py +322 -0
- core/runtime_config.py +96 -0
- core/runtime_paths.py +135 -0
- core/seams.py +35 -0
- core/secret_scan.py +25 -0
- core/secret_scrub.py +71 -0
- core/secrets.py +35 -0
- core/security_logging_filter.py +148 -0
- core/self_context_writer.py +333 -0
- core/self_evolution.py +294 -0
- core/session_context.py +47 -0
- core/sqlite_util.py +59 -0
- core/surfaces/__init__.py +16 -0
- core/surfaces/access.py +118 -0
- core/surfaces/binding.py +88 -0
- core/surfaces/bootstrap.py +78 -0
- core/surfaces/circuit.py +168 -0
- core/surfaces/continuity.py +77 -0
- core/surfaces/correspondents.py +228 -0
- core/surfaces/dispatcher.py +216 -0
- core/surfaces/envelopes.py +75 -0
- core/surfaces/gc.py +30 -0
- core/surfaces/idempotency.py +57 -0
- core/surfaces/inbound_webhook.py +124 -0
- core/surfaces/media.py +32 -0
- core/surfaces/message_router.py +91 -0
- core/surfaces/outbound_allowlist.py +80 -0
- core/surfaces/outbound_dispatcher.py +110 -0
- core/surfaces/outbound_mirror.py +37 -0
- core/surfaces/outbound_queue.py +107 -0
- core/surfaces/outbound_target.py +12 -0
- core/surfaces/owner_admin.py +29 -0
- core/surfaces/proactive.py +78 -0
- core/surfaces/progress.py +108 -0
- core/surfaces/rate_bucket.py +21 -0
- core/surfaces/registry.py +51 -0
- core/surfaces/rendering.py +53 -0
- core/surfaces/send_policy.py +14 -0
- core/surfaces/serialize.py +24 -0
- core/surfaces/session_chat_registry.py +112 -0
- core/surfaces/session_policy.py +60 -0
- core/surfaces/surface.py +288 -0
- core/surfaces/transcription.py +88 -0
- core/surfaces/voice_echo.py +26 -0
- core/surfaces/voice_guard.py +23 -0
- core/tickers.py +174 -0
- core/tool_catalog.py +192 -0
- core/version.py +75 -0
- core/wallet/__init__.py +5 -0
- core/wallet/agent_wallet.py +59 -0
- core/wallet/audit_sink.py +61 -0
- core/wallet/config.py +70 -0
- core/wallet/factory.py +87 -0
- core/wallet/policy.py +104 -0
- core/wallet/signer.py +51 -0
- cron/README.md +65 -0
- cron/__init__.py +0 -0
- cron/delivery.py +277 -0
- cron/digest.py +124 -0
- cron/jobs.py +207 -0
- cron/runner.py +297 -0
- cron/schedule.py +194 -0
- cron/scheduler.py +201 -0
- cron/service.py +57 -0
- cron/wake_gate.py +223 -0
- data/__init__.py +1 -0
- data/prompts/autov2_prompts.json +87 -0
- data/prompts/skills/browser-automation/SKILL.md +43 -0
- data/prompts/skills/coding-workflow/SKILL.md +36 -0
- data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
- data/prompts/skills/document-writing/SKILL.md +33 -0
- data/prompts/skills/email-comms/SKILL.md +33 -0
- data/prompts/skills/file-data-ops/SKILL.md +35 -0
- data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
- data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
- data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
- data/prompts/skills/lead-research/SKILL.md +86 -0
- data/prompts/skills/market-research-brief/SKILL.md +104 -0
- data/prompts/skills/person-analyzer/SKILL.md +124 -0
- data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
- data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
- data/prompts/skills/polymarket-trading/SKILL.md +55 -0
- data/prompts/skills/presentation-creator/SKILL.md +36 -0
- data/prompts/skills/project-analyzer/SKILL.md +169 -0
- data/prompts/skills/rules.json +268 -0
- data/prompts/skills/secret-handling/SKILL.md +20 -0
- data/prompts/skills/skill-authoring/SKILL.md +31 -0
- data/prompts/skills/skill-security-review/SKILL.md +27 -0
- data/prompts/skills/social-discovery/SKILL.md +57 -0
- data/prompts/skills/task-planning/SKILL.md +33 -0
- data/prompts/skills/web-research/SKILL.md +39 -0
- data/prompts/skills/web-scraping/SKILL.md +73 -0
- data/prompts/skills/x-engagement/SKILL.md +69 -0
- data/prompts/system_prompts.json +4 -0
- data/subscription_channels.json +5 -0
- modules/README.md +503 -0
- modules/__init__.py +124 -0
- modules/auth/__init__.py +13 -0
- modules/auth/api_key_manager.py +157 -0
- modules/auth/identity_mapper.py +380 -0
- modules/auth/siwe_auth.py +273 -0
- modules/auth/tier_manager.py +173 -0
- modules/base_module.py +190 -0
- modules/credits/__init__.py +29 -0
- modules/credits/balance_manager.py +237 -0
- modules/credits/cost_utils.py +129 -0
- modules/credits/pricing.py +139 -0
- modules/credits/unified_ledger.py +148 -0
- modules/credits/usage_meter.py +310 -0
- modules/credits/usage_tracker.py +674 -0
- modules/database/__init__.py +33 -0
- modules/database/audit_log.py +322 -0
- modules/database/auth_tables.py +276 -0
- modules/database/connection.py +644 -0
- modules/database/connection_pool.py +305 -0
- modules/database/conversation_contexts.py +446 -0
- modules/database/database_manager.py +237 -0
- modules/database/hyperliquid.py +375 -0
- modules/database/polymarket.py +574 -0
- modules/database/user_mcp_servers.py +728 -0
- modules/database/user_profiles.py +562 -0
- modules/database/utils.py +107 -0
- modules/database/x402_tables.py +92 -0
- modules/eip8004/README.md +1069 -0
- modules/eip8004/__init__.py +65 -0
- modules/eip8004/contracts.py +498 -0
- modules/eip8004/models.py +271 -0
- modules/eip8004/registration.py +148 -0
- modules/eip8004/reputation.py +367 -0
- modules/eip8004/validation.py +277 -0
- modules/llm/__init__.py +97 -0
- modules/llm/adapters.py +963 -0
- modules/llm/anthropic_client.py +831 -0
- modules/llm/available_models.py +106 -0
- modules/llm/brain_scrubber.py +137 -0
- modules/llm/cache_hints.py +162 -0
- modules/llm/deepseek_client.py +562 -0
- modules/llm/gemini_client.py +1367 -0
- modules/llm/llm_client.py +1139 -0
- modules/llm/llm_client_registry.py +188 -0
- modules/llm/llm_factory.py +237 -0
- modules/llm/llm_manager.py +1036 -0
- modules/llm/messages.py +374 -0
- modules/llm/model_registry.py +1935 -0
- modules/llm/nvidia_client.py +125 -0
- modules/llm/openai_client.py +800 -0
- modules/llm/openrouter_client.py +969 -0
- modules/llm/profiles.py +178 -0
- modules/llm/test_anthropic_cache_capture.py +35 -0
- modules/llm/test_cached_pricing_derivation.py +24 -0
- modules/llm/think_scrubber.py +444 -0
- modules/llm/token_counter.py +268 -0
- modules/memory/__init__.py +35 -0
- modules/memory/backend_factory.py +83 -0
- modules/memory/cache_manager.py +186 -0
- modules/memory/episodic.py +108 -0
- modules/memory/local_vector_memory_provider.py +552 -0
- modules/memory/memory_manager.py +225 -0
- modules/memory/models.py +312 -0
- modules/memory/provider.py +210 -0
- modules/memory/registry.py +205 -0
- modules/memory/sqlite_memory_provider.py +767 -0
- modules/memory/task/__init__.py +52 -0
- modules/memory/task/compaction_manager.py +157 -0
- modules/memory/task/context_retriever.py +809 -0
- modules/memory/task/hierarchical_memory.py +1002 -0
- modules/memory/task/lexical_retriever.py +71 -0
- modules/memory/task/null_context_manager.py +213 -0
- modules/memory/task/phase_manager.py +625 -0
- modules/memory/task/reflection_service.py +157 -0
- modules/memory/task/semantic_retriever.py +218 -0
- modules/memory/task/task_context_manager.py +1165 -0
- modules/memory/task/test_forgetting_engages.py +24 -0
- modules/memory/task/test_reflection_llm.py +39 -0
- modules/memory/task/test_threat_scan.py +53 -0
- modules/memory/task/threat_scan.py +90 -0
- modules/memory/test_sqlite_memory_provider.py +106 -0
- modules/memory/user_profile_manager.py +527 -0
- modules/payments/__init__.py +11 -0
- modules/payments/deposit_monitor.py +478 -0
- modules/payments/price_oracle.py +70 -0
- modules/payments/treasury_sweeper.py +401 -0
- modules/payments/wallet_generator.py +145 -0
- modules/pfp/__init__.py +1 -0
- modules/pfp/config.py +92 -0
- modules/pfp/mesh.py +565 -0
- modules/pfp/push.py +69 -0
- modules/pfp/renderer.py +85 -0
- modules/pfp/store.py +82 -0
- modules/skills/__init__.py +1 -0
- modules/skills/skill_usage.py +248 -0
- modules/transcription/__init__.py +24 -0
- modules/transcription/base.py +20 -0
- modules/transcription/faster_whisper_transcriber.py +55 -0
- modules/x402/README.md +249 -0
- modules/x402/__init__.py +22 -0
- modules/x402/invoicing.py +416 -0
- modules/x402/middleware.py +364 -0
- modules/x402/settlement_watcher.py +145 -0
- modules/x402/x402_integration.py +344 -0
- polyrob-0.5.0.dist-info/METADATA +482 -0
- polyrob-0.5.0.dist-info/RECORD +745 -0
- polyrob-0.5.0.dist-info/WHEEL +5 -0
- polyrob-0.5.0.dist-info/entry_points.txt +2 -0
- polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
- polyrob-0.5.0.dist-info/top_level.txt +12 -0
- surfaces/README.md +70 -0
- surfaces/__init__.py +0 -0
- surfaces/email/__init__.py +0 -0
- surfaces/email/dedup.py +44 -0
- surfaces/email/harness.py +187 -0
- surfaces/email/inbound.py +159 -0
- surfaces/email/seed.py +60 -0
- surfaces/email/surface.py +105 -0
- surfaces/telegram/__init__.py +0 -0
- surfaces/telegram/dedup.py +83 -0
- surfaces/telegram/harness.py +886 -0
- surfaces/telegram/inbound.py +145 -0
- surfaces/telegram/interactive_tools.py +49 -0
- surfaces/telegram/markdown.py +347 -0
- surfaces/telegram/rate_limit.py +167 -0
- surfaces/telegram/surface.py +187 -0
- surfaces/telegram/voice.py +52 -0
- surfaces/whatsapp/__init__.py +0 -0
- surfaces/whatsapp/client.py +51 -0
- surfaces/whatsapp/harness.py +62 -0
- surfaces/whatsapp/inbound.py +132 -0
- surfaces/whatsapp/surface.py +64 -0
- surfaces/whatsapp/window.py +34 -0
- tools/README.md +578 -0
- tools/__init__.py +424 -0
- tools/alchemy/__init__.py +5 -0
- tools/alchemy/alchemy_tool.py +389 -0
- tools/anysite/__init__.py +17 -0
- tools/anysite/client.py +88 -0
- tools/anysite/tool.py +89 -0
- tools/base_tool.py +468 -0
- tools/browser/__init__.py +22 -0
- tools/browser/actions.py +118 -0
- tools/browser/browser.py +1457 -0
- tools/browser/browser_manager.py +690 -0
- tools/browser/context.py +1748 -0
- tools/browser/playwright_utils.py +137 -0
- tools/browser/views.py +56 -0
- tools/code_exec/SANDBOX_SECURITY.md +144 -0
- tools/code_exec/__init__.py +178 -0
- tools/code_exec/backend.py +73 -0
- tools/code_exec/backends/__init__.py +1 -0
- tools/code_exec/backends/docker.py +779 -0
- tools/code_exec/backends/local_subprocess.py +154 -0
- tools/code_exec/env_policy.py +45 -0
- tools/code_exec/result.py +42 -0
- tools/code_exec/sandbox_guard.py +59 -0
- tools/code_exec/tool.py +227 -0
- tools/coding/__init__.py +50 -0
- tools/coding/edit.py +107 -0
- tools/coding/search.py +113 -0
- tools/coding/tool.py +353 -0
- tools/collabland/__init__.py +5 -0
- tools/collabland/collabland_tool.py +471 -0
- tools/controller/__init__.py +0 -0
- tools/controller/_helpers.py +165 -0
- tools/controller/action_registration.py +1765 -0
- tools/controller/approval.py +244 -0
- tools/controller/approval_interactive.py +90 -0
- tools/controller/delegation.py +195 -0
- tools/controller/execution.py +797 -0
- tools/controller/execution_context.py +141 -0
- tools/controller/hooks.py +135 -0
- tools/controller/introspection.py +344 -0
- tools/controller/mcp_registrar.py +190 -0
- tools/controller/message_send.py +31 -0
- tools/controller/registry/__init__.py +4 -0
- tools/controller/registry/schema_generators.py +395 -0
- tools/controller/registry/schema_sanitizer.py +406 -0
- tools/controller/registry/service.py +1266 -0
- tools/controller/registry/views.py +151 -0
- tools/controller/service.py +409 -0
- tools/controller/tool_management.py +334 -0
- tools/controller/types.py +28 -0
- tools/controller/views.py +450 -0
- tools/cronjob_tools.py +166 -0
- tools/crypto_trade_gate.py +59 -0
- tools/descriptors.py +452 -0
- tools/dom/__init__.py +0 -0
- tools/dom/history_tree_processor/service.py +244 -0
- tools/dom/history_tree_processor/view.py +99 -0
- tools/dom/service.py +471 -0
- tools/dom/views.py +346 -0
- tools/email_tool.py +403 -0
- tools/exceptions.py +169 -0
- tools/filesystem.py +1038 -0
- tools/filesystem_docproc.py +331 -0
- tools/filesystem_pdf.py +755 -0
- tools/git/__init__.py +42 -0
- tools/git/tool.py +258 -0
- tools/github/__init__.py +33 -0
- tools/github/client.py +94 -0
- tools/github/tool.py +237 -0
- tools/goal_tools.py +309 -0
- tools/hyperliquid/__init__.py +56 -0
- tools/hyperliquid/models.py +233 -0
- tools/hyperliquid/service.py +1680 -0
- tools/knowledge_ingest.py +950 -0
- tools/mcp/README.md +535 -0
- tools/mcp/__init__.py +52 -0
- tools/mcp/catalog.py +115 -0
- tools/mcp/config.py +337 -0
- tools/mcp/mcp_tool.py +1415 -0
- tools/mcp/param_coercion.py +289 -0
- tools/mcp/protocol.py +1520 -0
- tools/mcp/rate_limit.py +50 -0
- tools/mcp/security.py +424 -0
- tools/mcp/self_install.py +111 -0
- tools/mcp/server_manager.py +829 -0
- tools/mcp/subscriptions.py +72 -0
- tools/mcp/user_mcp_service.py +899 -0
- tools/mcp/validation_tracker.py +89 -0
- tools/mcp/views.py +346 -0
- tools/oauth/__init__.py +17 -0
- tools/oauth/manager.py +75 -0
- tools/oauth/provider.py +69 -0
- tools/oauth/providers/__init__.py +1 -0
- tools/oauth/providers/generic_oauth2.py +96 -0
- tools/perplexity_tool.py +280 -0
- tools/polymarket/__init__.py +41 -0
- tools/polymarket/clob_adapter.py +76 -0
- tools/polymarket/models.py +182 -0
- tools/polymarket/service.py +2082 -0
- tools/self_env/__init__.py +61 -0
- tools/self_env/tool.py +274 -0
- tools/shell/__init__.py +80 -0
- tools/shell/backend_pool.py +118 -0
- tools/shell/discipline.py +62 -0
- tools/shell/executor.py +157 -0
- tools/shell/loopback_allow.py +87 -0
- tools/shell/process_registry.py +113 -0
- tools/shell/process_tool.py +131 -0
- tools/shell/state.py +129 -0
- tools/shell/tool.py +151 -0
- tools/task_tool.py +637 -0
- tools/twitter_tool.py +2086 -0
- tools/user_directory.py +179 -0
- tools/web_fetch/__init__.py +3 -0
- tools/web_fetch/fetcher.py +144 -0
- tools/web_fetch/render.py +44 -0
- tools/web_fetch/tool.py +60 -0
- tools/x402/__init__.py +60 -0
- tools/x402/client.py +47 -0
- tools/x402/invoice_tool.py +143 -0
- tools/x402/real_client.py +250 -0
- tools/x402/service.py +131 -0
- utils/README.md +498 -0
- utils/__init__.py +46 -0
- utils/auth_utils.py +114 -0
- utils/bounded_collections.py +162 -0
- utils/circuit_breaker.py +389 -0
- utils/gif_utils.py +435 -0
- utils/markdown_utils.py +291 -0
- utils/message_utils.py +147 -0
- utils/metrics.py +36 -0
- utils/path_validator.py +227 -0
- utils/rate_limit_manager.py +399 -0
- utils/result_size.py +296 -0
- utils/time_utils.py +206 -0
- utils/user_utils.py +188 -0
- webview/README.md +111 -0
- webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
- webview/__init__.py +8 -0
- webview/activity.py +636 -0
- webview/owner_auth.py +108 -0
- webview/pages.py +448 -0
- webview/repair_sessions.py +399 -0
- webview/server.py +4323 -0
- webview/server_launcher.py +112 -0
- webview/static/css/activity.css +89 -0
- webview/static/css/chat.css +1772 -0
- webview/static/css/components.css +1190 -0
- webview/static/css/config-panel.css +651 -0
- webview/static/css/pages/admin.css +766 -0
- webview/static/css/pages/profile.css +231 -0
- webview/static/css/pages/settings.css +297 -0
- webview/static/css/pages/signin.css +316 -0
- webview/static/css/style.css +4250 -0
- webview/static/css/variables.css +147 -0
- webview/static/css/workspace-fullwidth-fix.css +109 -0
- webview/static/img/favicon.ico +0 -0
- webview/static/js/activity.js +394 -0
- webview/static/js/admin/activity.js +186 -0
- webview/static/js/admin/dashboard.js +163 -0
- webview/static/js/admin/user_detail.js +400 -0
- webview/static/js/admin/users.js +174 -0
- webview/static/js/admin/utils.js +214 -0
- webview/static/js/chat.js +3178 -0
- webview/static/js/config-panel.js +522 -0
- webview/static/js/constants.js +20 -0
- webview/static/js/error-handler.js +226 -0
- webview/static/js/ethers.min.js +1 -0
- webview/static/js/event-filter.js +137 -0
- webview/static/js/event-store.js +351 -0
- webview/static/js/file-attachments.js +374 -0
- webview/static/js/file-loader.js +163 -0
- webview/static/js/index.js +103 -0
- webview/static/js/performance-utils.js +196 -0
- webview/static/js/profile.js +396 -0
- webview/static/js/screenshot.js +386 -0
- webview/static/js/session.js +1527 -0
- webview/static/js/settings.js +1288 -0
- webview/static/js/sidebar-data.js +766 -0
- webview/static/js/sidebar-toggle.js +296 -0
- webview/static/js/socket.io.min.js +7 -0
- webview/static/js/stats.js +355 -0
- webview/static/js/ui-utils.js +420 -0
- webview/static/js/workspace.js +966 -0
- webview/stats_service.py +613 -0
- webview/templates/__init__.py +1 -0
- webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- webview/templates/activity.html +36 -0
- webview/templates/admin/activity.html +122 -0
- webview/templates/admin/dashboard.html +112 -0
- webview/templates/admin/user_detail.html +228 -0
- webview/templates/admin/users.html +105 -0
- webview/templates/autonomy.html +60 -0
- webview/templates/error.html +133 -0
- webview/templates/finance.html +90 -0
- webview/templates/identity.html +73 -0
- webview/templates/index.html +42 -0
- webview/templates/layout.html +125 -0
- webview/templates/memory.html +59 -0
- webview/templates/owner_login.html +15 -0
- webview/templates/profile.html +121 -0
- webview/templates/session.html +312 -0
- webview/templates/settings.html +534 -0
- webview/templates/sidebar.html +112 -0
- webview/templates/signin.html +213 -0
- webview/templates/status.html +14 -0
- webview/templates/system.html +45 -0
- webview/webgate.py +196 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""R1: conversational-exit policy for the agent run loop.
|
|
2
|
+
|
|
3
|
+
Pure helpers so the policy is unit-testable without a live Agent. The run loop
|
|
4
|
+
ends a turn when the agent keeps producing user-facing replies without calling
|
|
5
|
+
done() — otherwise a greeting/chat answer loops and re-greets (the 2026-06-14
|
|
6
|
+
Kimi anomaly). The rule is intentionally conservative:
|
|
7
|
+
|
|
8
|
+
* A turn ends only after ``CONVERSATIONAL_EXIT_AFTER_REPLIES`` *consecutive*
|
|
9
|
+
reply-only steps (no tool actually ran in any of them).
|
|
10
|
+
* Any productive step (a real tool result) OR a planning turn resets the
|
|
11
|
+
counter, so a task that does real work — even one that opens or closes with a
|
|
12
|
+
single standalone status message — is never cut short.
|
|
13
|
+
* Sub-agents never take this path; their turn boundary is owned by the parent.
|
|
14
|
+
|
|
15
|
+
A *blocking* send_message and done() already end the turn via ``is_done`` and are
|
|
16
|
+
not handled here (they are not tagged ``conversational_reply``).
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Iterable
|
|
21
|
+
|
|
22
|
+
# Number of back-to-back reply-only steps that ends a turn. 2 (not 1) leaves room
|
|
23
|
+
# for a task to follow a leading status message with real work on the next step.
|
|
24
|
+
CONVERSATIONAL_EXIT_AFTER_REPLIES = 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_reply_only_step(results: Iterable) -> bool:
|
|
28
|
+
"""True if the step produced ≥1 result and EVERY result is a non-blocking
|
|
29
|
+
user-facing reply (tagged ``metadata.conversational_reply``) — i.e. no tool
|
|
30
|
+
actually executed this step.
|
|
31
|
+
"""
|
|
32
|
+
results = list(results or [])
|
|
33
|
+
if not results:
|
|
34
|
+
return False
|
|
35
|
+
return all(bool(getattr(r, "metadata", None)) and r.metadata.get("conversational_reply") for r in results)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def should_conversational_exit(consecutive_reply_steps: int, is_sub_agent: bool,
|
|
39
|
+
is_autonomous: bool = False) -> bool:
|
|
40
|
+
"""Whether the run loop should end the turn now (R1).
|
|
41
|
+
|
|
42
|
+
T2-08: an AUTONOMOUS session (goal/cron/planner-spawned) is exempt like a
|
|
43
|
+
sub-agent — there is no human chatting with it, so a run of status
|
|
44
|
+
send_messages is progress narration, not a greeting loop; cutting the turn
|
|
45
|
+
there ended missions mid-flight (and mislabeled the run's completion).
|
|
46
|
+
"""
|
|
47
|
+
if is_sub_agent or is_autonomous:
|
|
48
|
+
return False
|
|
49
|
+
return consecutive_reply_steps >= CONVERSATIONAL_EXIT_AFTER_REPLIES
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""WS-A capability gate — block high-impact tools while a session is "tainted" by
|
|
2
|
+
untrusted correspondent DATA (Fusion HIGH/Q9).
|
|
3
|
+
|
|
4
|
+
Correspondent replies enter as a user-role control message with prompt-level framing
|
|
5
|
+
(`<correspondent-message>` + `<untrusted_tool_result>`). That framing is a SOFT defense:
|
|
6
|
+
a strong injection could still try to drive a tool. This pre-tool-call hook is the
|
|
7
|
+
STRUCTURAL backstop — while the latest input on a session is correspondent-untrusted,
|
|
8
|
+
the dangerous tools (money, outbound messaging, code execution, delegation, browser)
|
|
9
|
+
are denied, so a forged email can never directly spend/send/execute. The owner clears
|
|
10
|
+
the taint simply by sending a genuine message (they are driving again).
|
|
11
|
+
|
|
12
|
+
Pure policy + a hook factory; the taint flag lives on the orchestrator (set on
|
|
13
|
+
correspondent injection, cleared on owner intake).
|
|
14
|
+
|
|
15
|
+
⚠️ **tool_id vs action-name (the load-bearing detail).** The pre-tool-call hook receives
|
|
16
|
+
the bare *action name* — ``run_code``, ``goal_create``, ``x402_fetch`` — never the
|
|
17
|
+
*tool_id* (``code_execution``, ``goal``, ``x402_pay``). A denylist keyed on tool_ids is
|
|
18
|
+
therefore dead: it only ever fires for a tool whose single action is literally named
|
|
19
|
+
after the tool_id. So this module blocks in two layers:
|
|
20
|
+
|
|
21
|
+
1. ``_HIGH_IMPACT_NAMES`` — explicit high-impact *action* names (delegation, skill/
|
|
22
|
+
self-context, crypto trade verbs, git/github write verbs). Matched by name.
|
|
23
|
+
2. ``HIGH_IMPACT_TOOL_IDS`` — whole tools whose every non-read action is high-impact,
|
|
24
|
+
matched by RESOLVING the action's owning tool_id at hook time (via a resolver the
|
|
25
|
+
wiring passes in, backed by ``Controller.get_action_details(name).tool``). This is
|
|
26
|
+
the only way to cover dynamically-named actions such as MCP direct actions
|
|
27
|
+
(``{server}_{tool}`` → tool_id ``mcp``).
|
|
28
|
+
|
|
29
|
+
Crypto tools (``hyperliquid``/``polymarket``) are deliberately EXCLUDED from
|
|
30
|
+
``HIGH_IMPACT_TOOL_IDS`` so their read verbs (``get_*``) stay allowed — a tainted
|
|
31
|
+
session may still answer "what's the price?"; their trade verbs are enumerated by name.
|
|
32
|
+
"""
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import logging
|
|
36
|
+
from typing import Any, Callable, Optional
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Layer 1 — high-impact *action* names (matched against the bare action name the
|
|
42
|
+
# pre-hook receives). Includes standalone actions (registered directly, with no
|
|
43
|
+
# owning container tool_id) plus enumerated verbs kept for defense-in-depth even
|
|
44
|
+
# when tool-id resolution is unavailable. Crypto READ verbs are deliberately absent.
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
_HIGH_IMPACT_NAMES = frozenset({
|
|
47
|
+
# Delegation actions (registered directly, not container tools).
|
|
48
|
+
"delegate_task", "subtask", "parallel_subtasks",
|
|
49
|
+
# Identity / self-evolution actions (registered directly).
|
|
50
|
+
"skill_manage", "self_context_manage",
|
|
51
|
+
# Gated outbound message-to-target action (registered directly, no owning
|
|
52
|
+
# tool_id) — owner/allowlist-checked send to telegram/email/whatsapp.
|
|
53
|
+
"message",
|
|
54
|
+
# Aspirational coding/self-evolution action names (no tool yet; harmless tokens).
|
|
55
|
+
"self_modify", "mcp_install",
|
|
56
|
+
# WS-5: self_env self-maintenance verbs (posture 2). Owner-only via the posture
|
|
57
|
+
# gate already, but a tainted session must never reach them either.
|
|
58
|
+
"self_env_install_dep", "self_env_patch_source", "self_env_restart_service",
|
|
59
|
+
"self_env_git_pull", "self_env_read_source", "shell_run",
|
|
60
|
+
# WS-2/3: process job-manager verbs — enumerated by NAME (parity with shell_run)
|
|
61
|
+
# so a tool-id-resolver fault can't let a tainted session kill/inspect the owner's
|
|
62
|
+
# background shell jobs.
|
|
63
|
+
"process_kill", "process_log", "process_poll", "process_list",
|
|
64
|
+
# P1-4: code-exec verb enumerated by NAME (parity with shell_run) so a resolver
|
|
65
|
+
# fault can't open arbitrary code execution to a tainted session (the code_execution
|
|
66
|
+
# tool_id below only helps when resolution succeeds).
|
|
67
|
+
"run_code",
|
|
68
|
+
# P1-4: the agent money verb — x402_invoice tool, verb x402_request — mints a
|
|
69
|
+
# payment request. The canonical forged-email social-engineering target; must be
|
|
70
|
+
# unreachable while correspondent-tainted (x402_fetch/x402_pay already are).
|
|
71
|
+
"x402_request",
|
|
72
|
+
# P1-4: outbound-egress verbs whose query params are an exfil channel (parity with
|
|
73
|
+
# web_fetch/browser, which are already blocked). anysite/perplexity reach the
|
|
74
|
+
# outside world with attacker-influenced arguments.
|
|
75
|
+
"anysite_api", "perplexity_search",
|
|
76
|
+
# P1-4: curated-memory write persists into FUTURE sessions' prompts — a durable
|
|
77
|
+
# injection-persistence channel if written while tainted. Gate the whole action
|
|
78
|
+
# (read too) — fail-closed; the owner clears taint by replying.
|
|
79
|
+
"memory",
|
|
80
|
+
# Crypto trade verbs (hyperliquid + polymarket share these). Reads (get_*) are
|
|
81
|
+
# intentionally excluded so a tainted session can still fetch prices/history.
|
|
82
|
+
"place_limit_order", "place_market_order", "cancel_order", "cancel_all_orders",
|
|
83
|
+
"update_leverage", "approve_agent", "revoke_agent",
|
|
84
|
+
# git/github write verbs — also covered by tool-id resolution, kept here so a
|
|
85
|
+
# resolver fault can't open the money/ship-code path.
|
|
86
|
+
"git_push", "github_open_pr", "github_merge_pr", "github_pr_comment",
|
|
87
|
+
"github_issue_create",
|
|
88
|
+
# The auto-paying x402 action (the tool_id is x402_pay; the verb is x402_fetch).
|
|
89
|
+
"x402_fetch",
|
|
90
|
+
# Legacy tool_id tokens kept so is_high_impact(tool_id) stays truthy for callers/
|
|
91
|
+
# tests that probe by tool_id. Real per-verb coverage of these tools comes from
|
|
92
|
+
# HIGH_IMPACT_TOOL_IDS resolution below.
|
|
93
|
+
"code_execution", "coding", "cronjob", "goal", "x402_pay",
|
|
94
|
+
"hyperliquid", "polymarket", "email", "twitter", "browser", "web_fetch",
|
|
95
|
+
"git", "github", "process", "tool_manage", "mcp",
|
|
96
|
+
"x402_invoice", "anysite", "perplexity", # P1-4 legacy tool_id tokens
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
# Back-compat public name (tests / other callers import HIGH_IMPACT_TOOLS).
|
|
100
|
+
HIGH_IMPACT_TOOLS = _HIGH_IMPACT_NAMES
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# Layer 2 — tool_ids whose EVERY action is high-impact, resolved at hook time from
|
|
104
|
+
# the action name. Crypto is intentionally absent (its reads must stay allowed; its
|
|
105
|
+
# trade verbs are in _HIGH_IMPACT_NAMES). Blocking a high-impact tool's read verbs
|
|
106
|
+
# too (e.g. goal_list, cronjob_list, git_log) while tainted is the safe/fail-closed
|
|
107
|
+
# direction — the owner clears the taint by replying.
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
HIGH_IMPACT_TOOL_IDS = frozenset({
|
|
110
|
+
"code_execution", # run_code — arbitrary code exec
|
|
111
|
+
"coding", # str_replace / apply_patch / run_tests / *_file — repo mutation
|
|
112
|
+
"cronjob", # cronjob_schedule / cancel — durable recurring runs
|
|
113
|
+
"goal", # goal_create / objective_* — durable autonomous work
|
|
114
|
+
"x402_pay", # x402_fetch — auto-pay
|
|
115
|
+
"email", # send_email — outbound comms
|
|
116
|
+
"twitter", # twitter_post / reply / quote / thread — outbound comms
|
|
117
|
+
"browser", # go_to_url / click_element / … — SSRF / exfil
|
|
118
|
+
"web_fetch", # fetch_url — outbound fetch (SSRF / exfil)
|
|
119
|
+
"git", # git_commit / clone / pull / push — ship code / identity
|
|
120
|
+
"github", # github_open_pr / merge / … — ship code / identity
|
|
121
|
+
"mcp", # execute_tool + dynamic {server}_{tool} — outbound / exec
|
|
122
|
+
"process", # background processes (aspirational)
|
|
123
|
+
"tool_manage", # dynamic-tool authoring (aspirational)
|
|
124
|
+
"shell", # WS-2: persistent sandbox shell — arbitrary command exec
|
|
125
|
+
"self_env", # WS-5: self-maintenance verbs — install/patch/restart/pull
|
|
126
|
+
# P1-4:
|
|
127
|
+
"x402_invoice", # x402_request / accounting — mint agent payment requests (money)
|
|
128
|
+
"anysite", # anysite_api — outbound structured-data egress (exfil channel)
|
|
129
|
+
"perplexity", # perplexity_search — outbound search egress (exfil channel)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
# Substrings that mark a high-impact action even if the exact name isn't enumerated
|
|
133
|
+
# (e.g. provider-prefixed MCP/web tools that reach the outside world). NOTE: do NOT add
|
|
134
|
+
# "trade" here — as a substring it falsely flags the read action get_trade_history while
|
|
135
|
+
# matching no real trade verb (those are enumerated above).
|
|
136
|
+
_HIGH_IMPACT_PREFIXES = ("mcp_", "browser_", "web_", "email_", "pay", "send_email")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def is_high_impact(action_name: Optional[str]) -> bool:
|
|
140
|
+
"""Name-only high-impact test (no tool_id resolution).
|
|
141
|
+
|
|
142
|
+
True if ``action_name`` is an enumerated high-impact action name (or legacy
|
|
143
|
+
tool_id token) or matches a high-impact substring. This is the backward-compatible
|
|
144
|
+
surface; the wired hook additionally resolves the owning tool_id via
|
|
145
|
+
:func:`is_high_impact_call`.
|
|
146
|
+
"""
|
|
147
|
+
if not action_name:
|
|
148
|
+
return False
|
|
149
|
+
name = str(action_name).strip().lower()
|
|
150
|
+
if name in _HIGH_IMPACT_NAMES:
|
|
151
|
+
return True
|
|
152
|
+
return any(name.startswith(p) or p in name for p in _HIGH_IMPACT_PREFIXES)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def is_high_impact_call(action_name: Optional[str], tool_id: Optional[str] = None) -> bool:
|
|
156
|
+
"""Full high-impact decision for one action call.
|
|
157
|
+
|
|
158
|
+
Blocks when the bare action name is high-impact (:func:`is_high_impact`) OR when the
|
|
159
|
+
action's owning ``tool_id`` is in :data:`HIGH_IMPACT_TOOL_IDS`. ``tool_id`` is the
|
|
160
|
+
resolved owning tool (``Controller.get_action_details(name).tool``); pass ``None``
|
|
161
|
+
when it can't be resolved, in which case the decision degrades to the name-only path.
|
|
162
|
+
"""
|
|
163
|
+
if is_high_impact(action_name):
|
|
164
|
+
return True
|
|
165
|
+
if tool_id and str(tool_id).strip().lower() in HIGH_IMPACT_TOOL_IDS:
|
|
166
|
+
return True
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def build_tool_resolver(controller: Any) -> Callable[[str], Optional[str]]:
|
|
171
|
+
"""Return ``action_name -> owning tool_id`` backed by a Controller.
|
|
172
|
+
|
|
173
|
+
Uses ``Controller.get_action_details(name).tool`` — the same registry seam the
|
|
174
|
+
untrusted-wrap uses to resolve an action's source tool. Never raises: a missing
|
|
175
|
+
controller, an unknown action, or a registry fault all degrade to ``None`` (the
|
|
176
|
+
caller then falls back to the name-only decision).
|
|
177
|
+
"""
|
|
178
|
+
def _resolve(action_name: str) -> Optional[str]:
|
|
179
|
+
if controller is None:
|
|
180
|
+
return None
|
|
181
|
+
try:
|
|
182
|
+
details = controller.get_action_details(action_name)
|
|
183
|
+
return getattr(details, "tool", None) if details is not None else None
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.debug("build_tool_resolver: get_action_details failed for %r: %s",
|
|
186
|
+
action_name, e)
|
|
187
|
+
return None
|
|
188
|
+
return _resolve
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def make_correspondent_gate_hook(
|
|
192
|
+
get_tainted: Callable[[], bool],
|
|
193
|
+
resolve_tool: Optional[Callable[[str], Optional[str]]] = None,
|
|
194
|
+
):
|
|
195
|
+
"""Pre-tool-call hook ``(action_name, params, context) -> Optional[str]``.
|
|
196
|
+
|
|
197
|
+
Returns a denial reason (string) for a high-impact tool while the session is tainted
|
|
198
|
+
by untrusted correspondent data; None otherwise. Fail-CLOSED: if the taint probe
|
|
199
|
+
raises, a high-impact tool is denied (we can't prove the session is clean).
|
|
200
|
+
|
|
201
|
+
``resolve_tool`` maps an action name to its owning tool_id so a tool_id-level
|
|
202
|
+
denylist (:data:`HIGH_IMPACT_TOOL_IDS`) actually fires — the pre-hook only ever sees
|
|
203
|
+
the bare action name. It is called defensively: a resolver fault degrades to the
|
|
204
|
+
name-only decision (never raises out of the hook, never silently opens a hole for a
|
|
205
|
+
name-level high-impact action).
|
|
206
|
+
"""
|
|
207
|
+
def _hook(action_name: str, params: Any, context: Any) -> Optional[str]:
|
|
208
|
+
tool_id: Optional[str] = None
|
|
209
|
+
if resolve_tool is not None:
|
|
210
|
+
try:
|
|
211
|
+
tool_id = resolve_tool(action_name)
|
|
212
|
+
except Exception as e: # resolution must never break the gate
|
|
213
|
+
logger.debug("correspondent gate tool resolve failed: %s", e)
|
|
214
|
+
tool_id = None
|
|
215
|
+
if not is_high_impact_call(action_name, tool_id):
|
|
216
|
+
return None
|
|
217
|
+
try:
|
|
218
|
+
tainted = bool(get_tainted())
|
|
219
|
+
except Exception as e: # fail-closed: can't prove clean -> deny the dangerous tool
|
|
220
|
+
logger.debug("correspondent gate taint probe failed (deny): %s", e)
|
|
221
|
+
tainted = True
|
|
222
|
+
if tainted:
|
|
223
|
+
return (f"'{action_name}' is blocked: the latest input is untrusted "
|
|
224
|
+
f"correspondent DATA — owner confirmation is required before a "
|
|
225
|
+
f"high-impact action.")
|
|
226
|
+
return None
|
|
227
|
+
return _hook
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Skill curator (W5, Reference-parity curator.py).
|
|
2
|
+
|
|
3
|
+
Periodic maintenance of self-authored skills so the library doesn't rot: unused
|
|
4
|
+
authored skills go stale, then archive (recoverable, never hard-deleted); reuse
|
|
5
|
+
reactivates them. A skill the agent created but never reloads is noise — the curator
|
|
6
|
+
is what keeps "writable skills" from becoming "write-only skills".
|
|
7
|
+
|
|
8
|
+
Phase 1 (no LLM, always-on when CURATOR_ENABLED): ``apply_automatic_transitions``
|
|
9
|
+
over ``created_by ∈ {agent, background_review}`` skills using the W2-D load_count:
|
|
10
|
+
unused + age > ``CURATOR_STALE_DAYS`` → stale; stale + age > ``CURATOR_ARCHIVE_DAYS``
|
|
11
|
+
→ archive (via SkillManager.delete_skill → ``.archived/``); reuse reactivates.
|
|
12
|
+
System/pinned skills are never touched (only agent-authored ones).
|
|
13
|
+
|
|
14
|
+
(A Phase-2 aux-LLM "merge near-duplicate skills into umbrellas" step was scaffolded
|
|
15
|
+
but never had a real merge policy — it was a logged no-op. It was removed (2026-06-29
|
|
16
|
+
dead-loop prune); re-add it under its own flag when a concrete merge policy exists.)
|
|
17
|
+
|
|
18
|
+
Wired as a lifespan ticker (interval ``CURATOR_INTERVAL_HOURS``). Fail-open; a
|
|
19
|
+
dry-run mode lets the first rollout observe transitions without applying them.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Dict, List, Optional
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
_AUTHORED = ["agent", "background_review"]
|
|
31
|
+
_DAY_S = 86400.0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SkillCurator:
|
|
35
|
+
def __init__(self, skill_manager: Any, usage_store: Any, *,
|
|
36
|
+
clock=time.time, dry_run: bool = False):
|
|
37
|
+
self.sm = skill_manager
|
|
38
|
+
self.usage = usage_store
|
|
39
|
+
self._now = clock
|
|
40
|
+
self.dry_run = dry_run
|
|
41
|
+
|
|
42
|
+
# --- Phase 1: automatic, no LLM -----------------------------------------
|
|
43
|
+
|
|
44
|
+
def apply_automatic_transitions(self) -> Dict[str, List[str]]:
|
|
45
|
+
"""Stale/archive/reactivate authored skills by age + reuse. Returns the plan.
|
|
46
|
+
|
|
47
|
+
Pure-ish: computes the transition for every authored skill, then (unless
|
|
48
|
+
dry_run) applies archives via SkillManager. ``stale``/``reactivate`` are
|
|
49
|
+
bookkeeping states recorded in curator_state; ``archive`` is the only
|
|
50
|
+
filesystem effect (and it is recoverable).
|
|
51
|
+
"""
|
|
52
|
+
from agents.task.constants import AutonomyConfig
|
|
53
|
+
stale_days = AutonomyConfig.curator_stale_days()
|
|
54
|
+
archive_days = AutonomyConfig.curator_archive_days()
|
|
55
|
+
now = self._now()
|
|
56
|
+
plan: Dict[str, List[str]] = {"stale": [], "archived": [], "reactivated": [], "kept": []}
|
|
57
|
+
|
|
58
|
+
rows = self.usage.list_authored(created_by=_AUTHORED)
|
|
59
|
+
for r in rows:
|
|
60
|
+
skill_id = r["skill_id"]
|
|
61
|
+
user_id = r["user_id"]
|
|
62
|
+
load_count = r.get("load_count", 0) or 0
|
|
63
|
+
created_at = r.get("created_at") or now
|
|
64
|
+
age_days = max(0.0, (now - created_at) / _DAY_S)
|
|
65
|
+
key = f"{user_id}/{skill_id}"
|
|
66
|
+
|
|
67
|
+
# SK-F2: a background-review-authored draft always lands in
|
|
68
|
+
# `.pending/` (quarantined, uncatalogued) — its load_count can
|
|
69
|
+
# NEVER rise because it isn't in rules.json to be loaded from.
|
|
70
|
+
# Age-based archiving would otherwise sweep up drafts nobody has
|
|
71
|
+
# reviewed yet. Resolve the actual file (active wins over pending
|
|
72
|
+
# if both somehow exist) and skip the row entirely while pending.
|
|
73
|
+
find_file = getattr(self.sm, "_find_skill_file", None)
|
|
74
|
+
if callable(find_file):
|
|
75
|
+
try:
|
|
76
|
+
resolved = find_file(user_id, skill_id)
|
|
77
|
+
except Exception:
|
|
78
|
+
resolved = None
|
|
79
|
+
if resolved is not None and ".pending" in Path(resolved).parts:
|
|
80
|
+
continue # pending = awaiting owner review, load_count can never rise
|
|
81
|
+
|
|
82
|
+
if load_count > 0:
|
|
83
|
+
# Reused → keep active; clear any prior stale mark.
|
|
84
|
+
if self._get_mark(key) == "stale":
|
|
85
|
+
plan["reactivated"].append(key)
|
|
86
|
+
if not self.dry_run:
|
|
87
|
+
self._set_mark(key, "active")
|
|
88
|
+
self._self_mod_ev("reactivate", skill_id, user_id)
|
|
89
|
+
else:
|
|
90
|
+
plan["kept"].append(key)
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# never reused
|
|
94
|
+
# P2-21: skip a skill already archived on a prior tick. list_authored still
|
|
95
|
+
# returns its provenance row (rows persist after archive), so without this
|
|
96
|
+
# guard the weekly curator re-archived the same skill every tick — re-calling
|
|
97
|
+
# delete_skill (which returns False, file already gone) and re-emitting a
|
|
98
|
+
# self_modification "archive" event each time.
|
|
99
|
+
if self._get_mark(key) == "archived":
|
|
100
|
+
continue
|
|
101
|
+
if age_days > archive_days:
|
|
102
|
+
plan["archived"].append(key)
|
|
103
|
+
if not self.dry_run:
|
|
104
|
+
try:
|
|
105
|
+
self.sm.delete_skill(skill_id, user_id=user_id, absorbed_into=None)
|
|
106
|
+
self._set_mark(key, "archived")
|
|
107
|
+
self._self_mod_ev("archive", skill_id, user_id)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.debug("curator archive failed for %s: %s", key, e)
|
|
110
|
+
elif age_days > stale_days:
|
|
111
|
+
plan["stale"].append(key)
|
|
112
|
+
if not self.dry_run:
|
|
113
|
+
self._set_mark(key, "stale")
|
|
114
|
+
else:
|
|
115
|
+
plan["kept"].append(key)
|
|
116
|
+
|
|
117
|
+
logger.info("curator phase-1: %s",
|
|
118
|
+
{k: len(v) for k, v in plan.items()})
|
|
119
|
+
return plan
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _self_mod_ev(action: str, skill_id: str, user_id: str) -> None:
|
|
123
|
+
"""T4-06: curator lifecycle transitions are self-modifications too —
|
|
124
|
+
record them on the durable event log. Fail-open."""
|
|
125
|
+
try:
|
|
126
|
+
from agents.task.telemetry.self_events import emit_self_modification
|
|
127
|
+
emit_self_modification(kind="skill", action=action, item_id=skill_id,
|
|
128
|
+
user_id=user_id or "", created_by="curator",
|
|
129
|
+
source="curator")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.debug("curator event emit skipped: %s", e)
|
|
132
|
+
|
|
133
|
+
# --- state ---------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
def _get_mark(self, key: str) -> Optional[str]:
|
|
136
|
+
try:
|
|
137
|
+
return self.usage.get_state(f"curator:{key}")
|
|
138
|
+
except Exception:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
def _set_mark(self, key: str, value: str) -> None:
|
|
142
|
+
try:
|
|
143
|
+
self.usage.set_state(f"curator:{key}", value)
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
def should_run(self) -> bool:
|
|
148
|
+
"""Idle/interval gate: only run once per CURATOR_INTERVAL_HOURS."""
|
|
149
|
+
from agents.task.constants import AutonomyConfig
|
|
150
|
+
interval_s = AutonomyConfig.curator_interval_hours() * 3600.0
|
|
151
|
+
last = self.usage.get_state("curator:last_run")
|
|
152
|
+
if last is None:
|
|
153
|
+
return True
|
|
154
|
+
try:
|
|
155
|
+
return (self._now() - float(last)) >= interval_s
|
|
156
|
+
except (TypeError, ValueError):
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
def mark_ran(self) -> None:
|
|
160
|
+
self._set_mark_raw("curator:last_run", str(self._now()))
|
|
161
|
+
|
|
162
|
+
def _set_mark_raw(self, key: str, value: str) -> None:
|
|
163
|
+
try:
|
|
164
|
+
self.usage.set_state(key, value)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
async def run_once(self) -> Dict[str, Any]:
|
|
169
|
+
"""One curator pass: Phase 1 automatic transitions + episodic retention prune.
|
|
170
|
+
Fail-open.
|
|
171
|
+
|
|
172
|
+
(Stays ``async`` for the ticker contract even though Phase 1 is synchronous.)
|
|
173
|
+
"""
|
|
174
|
+
result: Dict[str, Any] = {}
|
|
175
|
+
try:
|
|
176
|
+
result["transitions"] = self.apply_automatic_transitions()
|
|
177
|
+
self.mark_ran()
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error("curator run failed: %s", e, exc_info=True)
|
|
180
|
+
result["error"] = str(e)
|
|
181
|
+
self._prune_episodes()
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
def _prune_episodes(self) -> None:
|
|
185
|
+
"""Episodic activity-ledger retention sweep (Task 7), riding the curator's
|
|
186
|
+
own cadence — never the write path. Global sweep (all tenants); fail-open
|
|
187
|
+
so a memory-backend hiccup never breaks the curator tick."""
|
|
188
|
+
try:
|
|
189
|
+
from agents.task.constants import AutonomyConfig
|
|
190
|
+
from modules.memory.registry import get_memory_registry
|
|
191
|
+
prov = get_memory_registry().active()
|
|
192
|
+
if prov is not None and hasattr(prov, "prune_episodes"):
|
|
193
|
+
cutoff = int(self._now()) - AutonomyConfig.episodic_retention_days() * 86400
|
|
194
|
+
removed = prov.prune_episodes(older_than_ts=cutoff)
|
|
195
|
+
if removed:
|
|
196
|
+
logger.info("curator: pruned %d old episodes", removed)
|
|
197
|
+
except Exception:
|
|
198
|
+
logger.warning("episodic prune skipped", exc_info=True)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class CuratorTicker:
|
|
202
|
+
"""Periodically run the curator (interval-gated). Mirrors CronTicker/GoalTicker."""
|
|
203
|
+
|
|
204
|
+
def __init__(self, curator: SkillCurator, *, check_interval_seconds: int = 3600,
|
|
205
|
+
lock_path: Optional[str] = None):
|
|
206
|
+
self.curator = curator
|
|
207
|
+
self.check_interval_seconds = check_interval_seconds
|
|
208
|
+
self.lock_path = lock_path
|
|
209
|
+
|
|
210
|
+
async def run_forever(self, stop_event=None) -> None:
|
|
211
|
+
import asyncio
|
|
212
|
+
while not (stop_event is not None and stop_event.is_set()):
|
|
213
|
+
try:
|
|
214
|
+
if self.curator.should_run():
|
|
215
|
+
lock = None
|
|
216
|
+
if self.lock_path:
|
|
217
|
+
from cron.scheduler import TickLock
|
|
218
|
+
lock = TickLock(self.lock_path)
|
|
219
|
+
if not lock.acquire():
|
|
220
|
+
lock = None # another worker holds the tick this cycle; skip
|
|
221
|
+
else:
|
|
222
|
+
try:
|
|
223
|
+
await self.curator.run_once()
|
|
224
|
+
finally:
|
|
225
|
+
lock.release()
|
|
226
|
+
else:
|
|
227
|
+
await self.curator.run_once()
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.error("curator tick failed: %s", e, exc_info=True)
|
|
230
|
+
try:
|
|
231
|
+
await asyncio.wait_for(
|
|
232
|
+
stop_event.wait() if stop_event else asyncio.sleep(self.check_interval_seconds),
|
|
233
|
+
timeout=self.check_interval_seconds,
|
|
234
|
+
)
|
|
235
|
+
except Exception:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def build_curator_ticker(*, data_dir: str = "data", dry_run: bool = False) -> CuratorTicker:
|
|
240
|
+
"""Assemble the curator stack into a ticker the app lifespan can start/stop."""
|
|
241
|
+
import os
|
|
242
|
+
from agents.task.agent.skill_manager import get_skill_manager
|
|
243
|
+
from modules.skills.skill_usage import get_skill_usage_store
|
|
244
|
+
curator = SkillCurator(get_skill_manager(), get_skill_usage_store(data_dir), dry_run=dry_run)
|
|
245
|
+
return CuratorTicker(curator, lock_path=os.path.join(data_dir, "curator.tick.lock"))
|
|
246
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Session-start activity digest (2026-07-03).
|
|
2
|
+
|
|
3
|
+
Passive, read-only, pinned foundation message that puts the agent's OWN recent runs
|
|
4
|
+
in front of it — so it can answer 'what did I run in the last 8h?' without a tool call.
|
|
5
|
+
Scoped to owner/chat sessions ONLY (never goal/cron/sub-agent) to avoid a per-run token
|
|
6
|
+
tax + distraction. Fail-open.
|
|
7
|
+
|
|
8
|
+
NOTE on `kind not in ("chat",)`: goal/cron sessions are excluded by the CALLER (the
|
|
9
|
+
first-step wiring in `memory_prefetch.py::MemoryPrefetchMixin._maybe_inject_episodic_digest`
|
|
10
|
+
resolves `kind` via `agents.task.goals.autonomy_marker.is_autonomous(session_id)` and only
|
|
11
|
+
ever passes `kind="chat"` here after confirming the session is not autonomous — see that
|
|
12
|
+
module's docstring for why this can't be decided at Agent-construction time). This function
|
|
13
|
+
re-checks `kind` itself so it is also safe to call directly (as the tests below do) with an
|
|
14
|
+
explicit non-chat `kind`.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import time
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def build_activity_digest(*, user_id: Optional[str], kind: str,
|
|
26
|
+
is_sub_agent: bool, window_hours: int = 24,
|
|
27
|
+
limit: int = 10):
|
|
28
|
+
try:
|
|
29
|
+
from agents.task.constants import AutonomyConfig
|
|
30
|
+
if not AutonomyConfig.episodic_digest_inject():
|
|
31
|
+
return None
|
|
32
|
+
if is_sub_agent or kind not in ("chat",): # owner/chat sessions only
|
|
33
|
+
return None
|
|
34
|
+
from modules.memory.registry import memory_recall_episodes
|
|
35
|
+
since_ts = int(time.time()) - window_hours * 3600
|
|
36
|
+
# exclude_surfaced=True: an episode already delivered out-of-band (cron
|
|
37
|
+
# email/telegram/twitter, or a goal self-wake) doesn't need to be
|
|
38
|
+
# re-surfaced here too — recent_activity() (explicit query) still sees
|
|
39
|
+
# everything regardless of the surfaced flag.
|
|
40
|
+
rows = await memory_recall_episodes(user_id=user_id, since_ts=since_ts,
|
|
41
|
+
limit=limit, order="newest",
|
|
42
|
+
exclude_surfaced=True)
|
|
43
|
+
if not rows:
|
|
44
|
+
return None
|
|
45
|
+
lines, total = [], 0.0
|
|
46
|
+
for e in rows:
|
|
47
|
+
total += float(e.spend_usd or 0)
|
|
48
|
+
lines.append(f"- {e.kind}:{e.outcome or '?'} ${float(e.spend_usd or 0):.2f} "
|
|
49
|
+
f"\"{(e.task or '')[:60]}\"")
|
|
50
|
+
body = ("\n".join(lines)
|
|
51
|
+
+ f"\n({len(rows)} runs in last {window_hours}h, ${total:.2f} total). "
|
|
52
|
+
"Call recent_activity(since=\"8h\") for more/older.")
|
|
53
|
+
try:
|
|
54
|
+
from agents.task.agent.core.untrusted_wrap import wrap_untrusted
|
|
55
|
+
body = wrap_untrusted("recent_activity", body)
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
from modules.llm.messages import make_control_message, MessageOrigin
|
|
59
|
+
return make_control_message(
|
|
60
|
+
f"<recent_activity window=\"last {window_hours}h\">\n{body}\n</recent_activity>",
|
|
61
|
+
MessageOrigin.EPISODIC_DIGEST)
|
|
62
|
+
except Exception:
|
|
63
|
+
logger.warning("build_activity_digest failed", exc_info=True)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def build_mission_continuity(*, user_id: Optional[str], window_hours: int = 24,
|
|
68
|
+
limit: int = 8):
|
|
69
|
+
"""Carry recent autonomous activity INTO the next goal/cron tick (§7.5).
|
|
70
|
+
|
|
71
|
+
Mirror-image of ``build_activity_digest``: this one is FOR autonomous sessions
|
|
72
|
+
(the caller confirms ``is_autonomous`` and never sub-agent), so a tick knows what
|
|
73
|
+
it already did/attempted and stops re-deriving 'nothing new' from scratch. Gated
|
|
74
|
+
``AUTONOMOUS_CONTINUITY_BRIDGE`` (default OFF). Fail-open. Does NOT exclude
|
|
75
|
+
already-surfaced episodes — a tick genuinely needs to see everything it did.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
from agents.task.constants import AutonomyConfig
|
|
79
|
+
if not AutonomyConfig.autonomous_continuity_bridge():
|
|
80
|
+
return None
|
|
81
|
+
from modules.memory.registry import memory_recall_episodes
|
|
82
|
+
since_ts = int(time.time()) - window_hours * 3600
|
|
83
|
+
rows = await memory_recall_episodes(user_id=user_id, since_ts=since_ts,
|
|
84
|
+
limit=limit, order="newest")
|
|
85
|
+
if not rows:
|
|
86
|
+
return None
|
|
87
|
+
lines = [f"- {e.kind}:{e.outcome or '?'} \"{(e.task or '')[:70]}\"" for e in rows]
|
|
88
|
+
body = ("\n".join(lines)
|
|
89
|
+
+ f"\n({len(rows)} recent runs in last {window_hours}h). "
|
|
90
|
+
"Build on this — do NOT repeat work already done; if it's all covered, "
|
|
91
|
+
"surface the blocker/next step rather than re-deriving 'nothing new'.")
|
|
92
|
+
try:
|
|
93
|
+
from agents.task.agent.core.untrusted_wrap import wrap_untrusted
|
|
94
|
+
body = wrap_untrusted("recent_activity", body)
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
from modules.llm.messages import make_control_message, MessageOrigin
|
|
98
|
+
return make_control_message(
|
|
99
|
+
f"<mission_continuity window=\"last {window_hours}h\">\n{body}\n</mission_continuity>",
|
|
100
|
+
MessageOrigin.EPISODIC_DIGEST)
|
|
101
|
+
except Exception:
|
|
102
|
+
logger.warning("build_mission_continuity failed", exc_info=True)
|
|
103
|
+
return None
|