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,611 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from modules.llm.messages import (
|
|
7
|
+
AIMessage,
|
|
8
|
+
BaseMessage,
|
|
9
|
+
HumanMessage,
|
|
10
|
+
MessageOrigin,
|
|
11
|
+
SystemMessage,
|
|
12
|
+
ToolMessage,
|
|
13
|
+
make_control_message,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _transient_compaction_errors() -> tuple:
|
|
20
|
+
"""Exception types that mean "retry later", not "this prompt is bad" (Phase 0.3).
|
|
21
|
+
|
|
22
|
+
A rate-limit / auth blip / connection drop during summarization should ABORT
|
|
23
|
+
compaction and keep full context, not trigger permanent lossy fallback. Imported
|
|
24
|
+
lazily-at-module-load and tolerant of a missing symbol so a core.exceptions
|
|
25
|
+
refactor can't break import of this hot module.
|
|
26
|
+
"""
|
|
27
|
+
names = ("LLMRateLimitError", "LLMAuthenticationError", "LLMConnectionError")
|
|
28
|
+
import core.exceptions as _exc
|
|
29
|
+
types = tuple(t for t in (getattr(_exc, n, None) for n in names) if isinstance(t, type))
|
|
30
|
+
# TimeoutError is always transient; include the stdlib one as a floor.
|
|
31
|
+
return types + (TimeoutError,)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_TRANSIENT_COMPACTION_ERRORS = _transient_compaction_errors()
|
|
35
|
+
|
|
36
|
+
# Compaction payload tuning (Reference-parity upgrade — see
|
|
37
|
+
# docs/REFERENCE_VS_ROB_CONTEXT_SYSTEM_2026-06.md §9).
|
|
38
|
+
_PER_MSG_CAP = 3000 # head+tail char budget per ordinary message (was a hard [:500])
|
|
39
|
+
_TOOL_RESULT_CAP = 3000 # head+tail char budget for a tool result (was the literal "[Tool result]")
|
|
40
|
+
_TAIL_TOKEN_RATIO = 0.20 # protected-tail token budget = max_input_tokens * ratio (C3)
|
|
41
|
+
_MIN_KEEP_RECENT = 10 # floor for the protected tail
|
|
42
|
+
_THRASH_SAVINGS_FLOOR = 0.10 # if last 2 compactions each saved < this, stop trying (B4)
|
|
43
|
+
_STATIC_FALLBACK_CAP = 8000 # char ceiling for the deterministic fallback summary (A6)
|
|
44
|
+
_COMPACTED_MARKER = "[COMPACTED SESSION HISTORY]"
|
|
45
|
+
# Absolute char budget for a single summarization prompt (~150K tokens). When the middle
|
|
46
|
+
# exceeds this, summarize in iterative windows so a SMALL auxiliary model (A5) can't be
|
|
47
|
+
# overflowed — Reference-style chunked/iterative summarization. No data is dropped.
|
|
48
|
+
_SUMMARIZE_INPUT_BUDGET_CHARS = 600_000
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _head_tail(text: str, cap: int) -> str:
|
|
52
|
+
"""Truncate ``text`` to ``cap`` chars keeping the head AND tail (Reference-style).
|
|
53
|
+
|
|
54
|
+
A flat ``[:cap]`` throws away the end of a message, which is often where the
|
|
55
|
+
decision/result lives. Head+tail preserves both ends with an elision marker.
|
|
56
|
+
"""
|
|
57
|
+
if len(text) <= cap:
|
|
58
|
+
return text
|
|
59
|
+
half = max(1, cap // 2)
|
|
60
|
+
return f"{text[:half]}\n…[{len(text) - cap} chars elided]…\n{text[-half:]}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CompactorMixin:
|
|
64
|
+
# Empty slots so the composed MessageManager keeps its own __slots__ (no __dict__).
|
|
65
|
+
__slots__ = ()
|
|
66
|
+
|
|
67
|
+
def clear_history_keep_system(self, keep_last_n: int = 2) -> None:
|
|
68
|
+
"""Clear message history but keep system messages and last N messages.
|
|
69
|
+
|
|
70
|
+
This is used for loop detection recovery - removes context while preserving
|
|
71
|
+
essential system setup and recent conversation flow.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
keep_last_n: Number of most recent messages to keep (default 2)
|
|
75
|
+
"""
|
|
76
|
+
# SECURITY FIX: Use _history_lock to protect deque operations
|
|
77
|
+
with self._history_lock:
|
|
78
|
+
messages_to_keep = []
|
|
79
|
+
|
|
80
|
+
# Keep all SystemMessage instances
|
|
81
|
+
for managed_msg in self.history.messages:
|
|
82
|
+
msg = managed_msg.message
|
|
83
|
+
if isinstance(msg, SystemMessage):
|
|
84
|
+
messages_to_keep.append(managed_msg)
|
|
85
|
+
|
|
86
|
+
# Add last N messages if available
|
|
87
|
+
if len(self.history.messages) >= keep_last_n:
|
|
88
|
+
messages_to_keep.extend(list(self.history.messages)[-keep_last_n:])
|
|
89
|
+
|
|
90
|
+
# Clear and re-add kept messages
|
|
91
|
+
self.history.messages.clear()
|
|
92
|
+
for managed_msg in messages_to_keep:
|
|
93
|
+
self.history.messages.append(managed_msg)
|
|
94
|
+
|
|
95
|
+
# Recalculate token count
|
|
96
|
+
self.history.total_tokens = sum(m.metadata.input_tokens + (m.metadata.output_tokens or 0) for m in self.history.messages)
|
|
97
|
+
self.logger.info(f"Cleared history, kept {len(messages_to_keep)} messages ({self.history.total_tokens} tokens)")
|
|
98
|
+
|
|
99
|
+
def emergency_context_prune(self) -> None:
|
|
100
|
+
"""Emergency pruning of message history to recover from context overflow.
|
|
101
|
+
|
|
102
|
+
Keeps only:
|
|
103
|
+
1. System message
|
|
104
|
+
2. Initial task (first human message)
|
|
105
|
+
3. Last N messages with complete tool call pairs
|
|
106
|
+
|
|
107
|
+
CRITICAL: Preserves complete AIMessage+ToolMessage pairs to prevent sequence validation errors.
|
|
108
|
+
Hierarchical memory will be re-injected on next LLM call.
|
|
109
|
+
"""
|
|
110
|
+
self.logger.warning("🚨 EMERGENCY CONTEXT PRUNING INITIATED")
|
|
111
|
+
|
|
112
|
+
messages = list(self.history.messages)
|
|
113
|
+
original_count = len(messages)
|
|
114
|
+
original_tokens = self.history.total_tokens
|
|
115
|
+
|
|
116
|
+
if original_count <= 5:
|
|
117
|
+
self.logger.info("Message count already minimal, skipping prune")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
preserved = []
|
|
121
|
+
|
|
122
|
+
# Keep system message (if exists) - but it's stored separately, not in history.messages
|
|
123
|
+
# This is just for safety in case old code added it
|
|
124
|
+
remaining_messages = messages
|
|
125
|
+
if messages and isinstance(messages[0].message if hasattr(messages[0], 'message') else messages[0], SystemMessage):
|
|
126
|
+
preserved.append(messages[0])
|
|
127
|
+
remaining_messages = messages[1:]
|
|
128
|
+
|
|
129
|
+
# Keep initial task (first human message) - but it's also stored separately
|
|
130
|
+
# This is for safety in case old code added it
|
|
131
|
+
for i, managed_msg in enumerate(remaining_messages):
|
|
132
|
+
msg = managed_msg.message if hasattr(managed_msg, 'message') else managed_msg
|
|
133
|
+
if isinstance(msg, HumanMessage):
|
|
134
|
+
preserved.append(managed_msg)
|
|
135
|
+
remaining_messages = remaining_messages[i+1:]
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
# ✅ FIX: Keep last N messages with complete tool call pairs
|
|
139
|
+
# Start with last 3 messages and expand if needed to complete pairs
|
|
140
|
+
min_recent_messages = 3
|
|
141
|
+
recent_messages = []
|
|
142
|
+
|
|
143
|
+
# Work backwards from the end to collect complete tool call pairs
|
|
144
|
+
idx = len(remaining_messages) - 1
|
|
145
|
+
messages_collected = 0
|
|
146
|
+
pending_tool_call_ids = set() # Tool calls waiting for responses
|
|
147
|
+
|
|
148
|
+
while idx >= 0 and messages_collected < min_recent_messages + len(pending_tool_call_ids) * 2:
|
|
149
|
+
managed_msg = remaining_messages[idx]
|
|
150
|
+
msg = managed_msg.message if hasattr(managed_msg, 'message') else managed_msg
|
|
151
|
+
|
|
152
|
+
# Check if this is a ToolMessage
|
|
153
|
+
if isinstance(msg, ToolMessage) and hasattr(msg, 'tool_call_id'):
|
|
154
|
+
recent_messages.insert(0, managed_msg)
|
|
155
|
+
pending_tool_call_ids.add(msg.tool_call_id) # Mark that we need the corresponding AIMessage
|
|
156
|
+
messages_collected += 1
|
|
157
|
+
|
|
158
|
+
# Check if this is an AIMessage with tool_calls
|
|
159
|
+
elif isinstance(msg, AIMessage) and hasattr(msg, 'tool_calls') and msg.tool_calls:
|
|
160
|
+
recent_messages.insert(0, managed_msg)
|
|
161
|
+
messages_collected += 1
|
|
162
|
+
|
|
163
|
+
# Remove tool calls that we've already seen responses for
|
|
164
|
+
for tc in msg.tool_calls:
|
|
165
|
+
tc_id = tc.get('id') if isinstance(tc, dict) else getattr(tc, 'id', None)
|
|
166
|
+
if tc_id in pending_tool_call_ids:
|
|
167
|
+
pending_tool_call_ids.discard(tc_id)
|
|
168
|
+
else:
|
|
169
|
+
# This tool call doesn't have a response yet - we need to keep searching
|
|
170
|
+
# But we've already added this AIMessage, so just track it
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
# Regular message (HumanMessage, SystemMessage without tool calls, etc.)
|
|
175
|
+
recent_messages.insert(0, managed_msg)
|
|
176
|
+
messages_collected += 1
|
|
177
|
+
|
|
178
|
+
idx -= 1
|
|
179
|
+
|
|
180
|
+
# If we still have pending tool call IDs, log a warning
|
|
181
|
+
if pending_tool_call_ids:
|
|
182
|
+
self.logger.warning(
|
|
183
|
+
f"⚠️ Emergency prune: Could not find AIMessages for tool calls: {pending_tool_call_ids}. "
|
|
184
|
+
f"Message sequence may be invalid."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
preserved.extend(recent_messages)
|
|
188
|
+
|
|
189
|
+
# Reconstruct history
|
|
190
|
+
self.history.messages.clear()
|
|
191
|
+
self.history.total_tokens = 0
|
|
192
|
+
|
|
193
|
+
for managed_msg in preserved:
|
|
194
|
+
msg = managed_msg.message if hasattr(managed_msg, 'message') else managed_msg
|
|
195
|
+
# Re-add each message to recalculate tokens
|
|
196
|
+
self._add_message_with_tokens(msg, _internal=True)
|
|
197
|
+
|
|
198
|
+
new_count = len(self.history.messages)
|
|
199
|
+
new_tokens = self.history.total_tokens
|
|
200
|
+
|
|
201
|
+
# B4 recovery: an emergency prune materially changes the history, so any prior
|
|
202
|
+
# "compaction isn't helping" (anti-thrash) signal is now stale. Reset it so LLM
|
|
203
|
+
# compaction gets a fresh chance once usage climbs back into the 85-95% band.
|
|
204
|
+
self._compaction_savings = []
|
|
205
|
+
|
|
206
|
+
self.logger.warning(
|
|
207
|
+
f"✅ Emergency pruning complete:\n"
|
|
208
|
+
f" Messages: {original_count} → {new_count} (removed {original_count - new_count})\n"
|
|
209
|
+
f" Tokens: {original_tokens:,} → {new_tokens:,} (freed {original_tokens - new_tokens:,})\n"
|
|
210
|
+
f" Usage: {self.get_context_usage_percent():.1f}%"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# ------------------------------------------------------------------ #
|
|
214
|
+
# Compaction tail sizing (C3) + anti-thrash (B4)
|
|
215
|
+
# ------------------------------------------------------------------ #
|
|
216
|
+
def _compaction_keep_recent(self, messages: list, min_keep: int = _MIN_KEEP_RECENT) -> int:
|
|
217
|
+
"""How many recent messages to keep intact (token-budget OR count, generous).
|
|
218
|
+
|
|
219
|
+
C3: instead of a flat ``keep_recent=10``, protect the tail by a token budget
|
|
220
|
+
(``max_input_tokens * ratio``) when that protects MORE than the floor — so a
|
|
221
|
+
token-dense history keeps the meaningful recent turns, not just the last 10.
|
|
222
|
+
"""
|
|
223
|
+
max_in = int(getattr(self, "max_input_tokens", 0) or 0)
|
|
224
|
+
budget = int(max_in * _TAIL_TOKEN_RATIO)
|
|
225
|
+
if budget <= 0:
|
|
226
|
+
return min_keep
|
|
227
|
+
count = 0
|
|
228
|
+
used = 0
|
|
229
|
+
for msg in reversed(messages):
|
|
230
|
+
tokens = max(1, len(str(getattr(msg, "content", ""))) // 4)
|
|
231
|
+
if used + tokens > budget:
|
|
232
|
+
break
|
|
233
|
+
used += tokens
|
|
234
|
+
count += 1
|
|
235
|
+
return max(count, min_keep)
|
|
236
|
+
|
|
237
|
+
def _compaction_is_thrashing(self) -> bool:
|
|
238
|
+
"""B4: if the last two compactions each saved < floor, stop calling the LLM.
|
|
239
|
+
|
|
240
|
+
The ≥95% emergency prune (a non-LLM net) still runs from ``step.py`` — this
|
|
241
|
+
only suppresses the *expensive* LLM compaction when it has stopped helping.
|
|
242
|
+
"""
|
|
243
|
+
savings = getattr(self, "_compaction_savings", [])
|
|
244
|
+
recent = savings[-2:]
|
|
245
|
+
return len(recent) == 2 and all(s < _THRASH_SAVINGS_FLOOR for s in recent)
|
|
246
|
+
|
|
247
|
+
def _record_compaction_savings(self, original_tokens: int, new_tokens: int) -> None:
|
|
248
|
+
saved = (original_tokens - new_tokens) / max(original_tokens, 1)
|
|
249
|
+
savings = list(getattr(self, "_compaction_savings", []))
|
|
250
|
+
savings.append(max(0.0, saved))
|
|
251
|
+
self._compaction_savings = savings[-2:]
|
|
252
|
+
|
|
253
|
+
# ------------------------------------------------------------------ #
|
|
254
|
+
# Pre-compaction checkpoint (C2 — scoped-down lineage)
|
|
255
|
+
# ------------------------------------------------------------------ #
|
|
256
|
+
def _resolve_checkpoint_dir(self) -> Optional[str]:
|
|
257
|
+
explicit = getattr(self, "_compaction_checkpoint_dir", None)
|
|
258
|
+
if explicit:
|
|
259
|
+
return explicit
|
|
260
|
+
session_id = getattr(self, "session_id", None)
|
|
261
|
+
if not session_id:
|
|
262
|
+
return None
|
|
263
|
+
try:
|
|
264
|
+
from agents.task.path import pm
|
|
265
|
+
return str(pm().get_history_dir(session_id))
|
|
266
|
+
except Exception:
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
def _write_compaction_checkpoint(self, messages: list) -> None:
|
|
270
|
+
"""Persist the raw pre-compaction trajectory so it stays recoverable (C2)."""
|
|
271
|
+
checkpoint_dir = self._resolve_checkpoint_dir()
|
|
272
|
+
if not checkpoint_dir:
|
|
273
|
+
return
|
|
274
|
+
try:
|
|
275
|
+
import os
|
|
276
|
+
import json
|
|
277
|
+
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
278
|
+
n = getattr(self, "_compaction_count", 0)
|
|
279
|
+
path = os.path.join(checkpoint_dir, f"compaction_{n}.json")
|
|
280
|
+
payload = [
|
|
281
|
+
{"role": m.__class__.__name__, "content": str(getattr(m, "content", ""))}
|
|
282
|
+
for m in messages
|
|
283
|
+
]
|
|
284
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
285
|
+
json.dump(payload, fh, ensure_ascii=False)
|
|
286
|
+
self._compaction_count = n + 1
|
|
287
|
+
except Exception as exc: # never let audit I/O block compaction
|
|
288
|
+
self.logger.debug(f"compaction checkpoint skipped: {exc}")
|
|
289
|
+
|
|
290
|
+
# ------------------------------------------------------------------ #
|
|
291
|
+
# LLM compaction
|
|
292
|
+
# ------------------------------------------------------------------ #
|
|
293
|
+
async def _run_pre_compress_hook(self) -> None:
|
|
294
|
+
"""Invoke the active memory provider's on_pre_compress hook (P1-4).
|
|
295
|
+
|
|
296
|
+
Lets a provider persist anything in the volatile message tail that hasn't been
|
|
297
|
+
promoted to durable memory before compaction summarizes it away. Fail-open: a
|
|
298
|
+
hook error never blocks compaction. No-op under the default NullMemoryProvider.
|
|
299
|
+
"""
|
|
300
|
+
try:
|
|
301
|
+
from modules.memory.registry import get_memory_registry
|
|
302
|
+
provider = get_memory_registry().active()
|
|
303
|
+
if provider is None or not getattr(provider, "is_external", False):
|
|
304
|
+
return
|
|
305
|
+
await provider.on_pre_compress(session_id=getattr(self, "session_id", "") or "")
|
|
306
|
+
except Exception as e:
|
|
307
|
+
self.logger.debug(f"on_pre_compress hook skipped: {e}")
|
|
308
|
+
|
|
309
|
+
async def llm_compact_history(self) -> bool:
|
|
310
|
+
"""LLM-driven compaction of message history.
|
|
311
|
+
|
|
312
|
+
Reference-parity upgrade (see docs/REFERENCE_VS_ROB_CONTEXT_SYSTEM_2026-06.md):
|
|
313
|
+
- A1: feed the WHOLE middle to the summarizer (no silent [-50:] drop).
|
|
314
|
+
- A2: serialize tool results + raise the per-message cap (no [Tool result] literal).
|
|
315
|
+
- A3: structured, sectioned summary template scaled to the volume compacted.
|
|
316
|
+
- A4: iterative update — feed any prior compacted summary back in to update.
|
|
317
|
+
- A5: route to a cheap auxiliary model (``aux_llm``) when configured.
|
|
318
|
+
- A6: deterministic static fallback when the summarizer errors.
|
|
319
|
+
- B4: anti-thrash back-off; C2: pre-compaction checkpoint; C3: token-budget tail.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
True if compaction (or static fallback) was performed, False otherwise.
|
|
323
|
+
"""
|
|
324
|
+
original_messages = [m.message for m in self.history.messages]
|
|
325
|
+
original_count = len(original_messages)
|
|
326
|
+
original_tokens = self.history.total_tokens
|
|
327
|
+
|
|
328
|
+
if original_count < 15:
|
|
329
|
+
self.logger.debug("Not enough messages to compact")
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
# B4: stop burning an LLM call when the last two compactions barely helped.
|
|
333
|
+
if self._compaction_is_thrashing():
|
|
334
|
+
self.logger.info("Skipping LLM compaction (anti-thrash: last 2 saved < 10%)")
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
# C3: protect the tail by token budget OR count, whichever is more generous.
|
|
338
|
+
keep_recent = self._compaction_keep_recent(original_messages)
|
|
339
|
+
# CX-H2: never cut inside an AIMessage(tool_calls)→ToolMessage pair — a tail
|
|
340
|
+
# that starts with orphan ToolMessages gets them silently dropped by
|
|
341
|
+
# repair_tool_message_pairs on every subsequent LLM call. Pull leading
|
|
342
|
+
# orphans back into the summarized span.
|
|
343
|
+
while keep_recent < len(original_messages):
|
|
344
|
+
# D2: original_messages is already unwrapped to BaseMessage above
|
|
345
|
+
# (`[m.message for m in self.history.messages]`), so head IS the
|
|
346
|
+
# message — the old `hasattr(head, 'message')` unwrap branch was inert.
|
|
347
|
+
head_msg = original_messages[-keep_recent]
|
|
348
|
+
if isinstance(head_msg, ToolMessage):
|
|
349
|
+
keep_recent += 1 # extend the tail to include the owning AIMessage
|
|
350
|
+
else:
|
|
351
|
+
break
|
|
352
|
+
messages_to_summarize = original_messages[:-keep_recent]
|
|
353
|
+
recent_messages = original_messages[-keep_recent:]
|
|
354
|
+
|
|
355
|
+
if len(messages_to_summarize) < 5:
|
|
356
|
+
self.logger.debug("Not enough messages to summarize")
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
# C2: snapshot the raw trajectory before we mutate it.
|
|
360
|
+
self._write_compaction_checkpoint(original_messages)
|
|
361
|
+
|
|
362
|
+
# A4: separate any prior compacted summary so it is UPDATED, not re-summarized.
|
|
363
|
+
prior_summary: Optional[str] = None
|
|
364
|
+
to_summarize: list = []
|
|
365
|
+
for msg in messages_to_summarize:
|
|
366
|
+
text = str(getattr(msg, "content", ""))
|
|
367
|
+
origin = getattr(msg, "origin", "")
|
|
368
|
+
# Envelope-safe + reload-safe: match the typed origin OR the marker anywhere
|
|
369
|
+
# in the leading slice (the marker now sits just inside <compacted-history>).
|
|
370
|
+
if origin == MessageOrigin.COMPACTION_SUMMARY or _COMPACTED_MARKER in text[:160]:
|
|
371
|
+
prior_summary = text # keep the most recent prior summary
|
|
372
|
+
else:
|
|
373
|
+
to_summarize.append(msg)
|
|
374
|
+
|
|
375
|
+
# P1-4: give the active memory provider a chance to flush salient context
|
|
376
|
+
# before we summarize the middle away. Fail-open and inert by default
|
|
377
|
+
# (NullMemoryProvider's on_pre_compress is a no-op).
|
|
378
|
+
await self._run_pre_compress_hook()
|
|
379
|
+
|
|
380
|
+
# A5: prefer a cheap auxiliary model for compaction; fall back to the main LLM.
|
|
381
|
+
compaction_llm = getattr(self, "aux_llm", None) or self.llm
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
summary_text = await self._run_summarization(to_summarize, prior_summary, compaction_llm)
|
|
385
|
+
if not (summary_text and summary_text.strip()):
|
|
386
|
+
# An empty LLM summary would clear the history and insert an empty
|
|
387
|
+
# digest — permanent DATA LOSS. Fall back to the deterministic static
|
|
388
|
+
# summary; if even that is empty, abort and keep the FULL context (the
|
|
389
|
+
# >=95% emergency prune is the overflow backstop).
|
|
390
|
+
self.logger.warning("LLM compaction returned an empty summary; using static fallback")
|
|
391
|
+
static = self._build_static_fallback_summary(to_summarize)
|
|
392
|
+
if static and prior_summary:
|
|
393
|
+
static = f"{prior_summary}\n\n{static}"
|
|
394
|
+
if not (static and static.strip()):
|
|
395
|
+
self.logger.warning("Static fallback also empty; keeping full context, will retry")
|
|
396
|
+
return False
|
|
397
|
+
summary_text = static
|
|
398
|
+
self._rebuild_with_summary(summary_text, len(to_summarize), recent_messages)
|
|
399
|
+
self._record_compaction_savings(original_tokens, self.history.total_tokens)
|
|
400
|
+
|
|
401
|
+
self.logger.info(
|
|
402
|
+
f"✅ LLM compaction complete: {original_count} → {len(self.history.messages)} messages, "
|
|
403
|
+
f"{original_tokens:,} → {self.history.total_tokens:,} tokens"
|
|
404
|
+
)
|
|
405
|
+
return True
|
|
406
|
+
|
|
407
|
+
except _TRANSIENT_COMPACTION_ERRORS as e:
|
|
408
|
+
# Phase 0.3 (Hermes parity): a transient/credential failure must NOT cause
|
|
409
|
+
# permanent lossy compaction. Abort, keep the FULL context, and retry next
|
|
410
|
+
# step. The >=95% mechanical emergency prune is the overflow backstop, so
|
|
411
|
+
# this is safe even if the failure persists.
|
|
412
|
+
self.logger.warning(
|
|
413
|
+
f"LLM compaction aborted (transient: {type(e).__name__}: {e}); "
|
|
414
|
+
"keeping full context, will retry next step"
|
|
415
|
+
)
|
|
416
|
+
return False
|
|
417
|
+
|
|
418
|
+
except Exception as e:
|
|
419
|
+
# A6: build a deterministic summary locally instead of dropping straight to prune.
|
|
420
|
+
self.logger.error(f"LLM compaction failed: {e}, building static fallback summary")
|
|
421
|
+
static = self._build_static_fallback_summary(to_summarize)
|
|
422
|
+
# BUG 4 fix: preserve the prior compacted summary in the fallback so
|
|
423
|
+
# iterative compaction does not drop the earlier digest on a non-transient
|
|
424
|
+
# error. The happy path already passes prior_summary into _run_summarization;
|
|
425
|
+
# the except branch must do the same by prepending it to the static output.
|
|
426
|
+
if static and prior_summary:
|
|
427
|
+
static = f"{prior_summary}\n\n{static}"
|
|
428
|
+
if static:
|
|
429
|
+
self._rebuild_with_summary(static, len(to_summarize), recent_messages)
|
|
430
|
+
self._record_compaction_savings(original_tokens, self.history.total_tokens)
|
|
431
|
+
self.logger.warning("Static fallback summary applied (summarizer unavailable)")
|
|
432
|
+
return True
|
|
433
|
+
self.logger.error("Static fallback empty; falling back to emergency prune")
|
|
434
|
+
self.emergency_context_prune()
|
|
435
|
+
return True
|
|
436
|
+
|
|
437
|
+
def _window_messages(self, messages: list, budget_chars: int) -> list:
|
|
438
|
+
"""Split messages into ordered windows whose capped char-size each fit a budget."""
|
|
439
|
+
windows: list = []
|
|
440
|
+
current: list = []
|
|
441
|
+
size = 0
|
|
442
|
+
for msg in messages:
|
|
443
|
+
raw = str(getattr(msg, "content", ""))
|
|
444
|
+
cap = _TOOL_RESULT_CAP if "Tool" in msg.__class__.__name__ else _PER_MSG_CAP
|
|
445
|
+
msg_chars = min(len(raw), cap) + 20 # +role/label overhead
|
|
446
|
+
if current and size + msg_chars > budget_chars:
|
|
447
|
+
windows.append(current)
|
|
448
|
+
current = []
|
|
449
|
+
size = 0
|
|
450
|
+
current.append(msg)
|
|
451
|
+
size += msg_chars
|
|
452
|
+
if current:
|
|
453
|
+
windows.append(current)
|
|
454
|
+
return windows or [[]]
|
|
455
|
+
|
|
456
|
+
async def _run_summarization(self, messages: list, prior_summary: Optional[str], compaction_llm) -> str:
|
|
457
|
+
"""Summarize ``messages`` in one shot, or in iterative windows when huge (A1/A4).
|
|
458
|
+
|
|
459
|
+
Iterative windowing means a SMALL auxiliary model can summarize an arbitrarily
|
|
460
|
+
large middle without overflowing: each window is summarized with the running
|
|
461
|
+
summary fed back in (Reference-style), so nothing is dropped.
|
|
462
|
+
"""
|
|
463
|
+
windows = self._window_messages(messages, _SUMMARIZE_INPUT_BUDGET_CHARS)
|
|
464
|
+
if len(windows) > 1:
|
|
465
|
+
self.logger.info(f"Compaction input large; summarizing in {len(windows)} iterative windows")
|
|
466
|
+
running = prior_summary
|
|
467
|
+
for window in windows:
|
|
468
|
+
prompt = self._build_compaction_prompt(window, prior_summary=running)
|
|
469
|
+
import time as _time
|
|
470
|
+
_t0 = _time.time()
|
|
471
|
+
response = await compaction_llm.ainvoke([HumanMessage(content=prompt)])
|
|
472
|
+
# A3: meter this aux LLM call through the single deduction path (fail-open).
|
|
473
|
+
from agents.task.agent.core.aux_metering import meter_aux_llm
|
|
474
|
+
await meter_aux_llm(
|
|
475
|
+
usage_tracker=getattr(self, "usage_tracker", None),
|
|
476
|
+
user_id=getattr(self, "metering_user_id", None),
|
|
477
|
+
session_id=getattr(self, "session_id", ""),
|
|
478
|
+
agent_id=getattr(self, "metering_agent_id", "") or "",
|
|
479
|
+
llm=compaction_llm, response=response, duration_seconds=_time.time() - _t0,
|
|
480
|
+
component="compaction", purpose="compaction",
|
|
481
|
+
)
|
|
482
|
+
running = response.content if hasattr(response, 'content') else str(response)
|
|
483
|
+
return running or ""
|
|
484
|
+
|
|
485
|
+
def _rebuild_with_summary(self, summary_text: str, summarized_count: int, recent_messages: list) -> None:
|
|
486
|
+
"""Replace the summarized middle with one compacted message + recent tail.
|
|
487
|
+
|
|
488
|
+
The summary is a LOSSY, LLM-synthesized digest — NOT a user turn. Tag it with
|
|
489
|
+
MessageOrigin.COMPACTION_SUMMARY and an <compacted-history> envelope so the model
|
|
490
|
+
reads it as derived context, not an instruction (the system prompt's source-precedence
|
|
491
|
+
rule ranks it below recent messages / current files).
|
|
492
|
+
"""
|
|
493
|
+
body = (f"{_COMPACTED_MARKER}\n\n"
|
|
494
|
+
f"The following is a structured summary of {summarized_count} earlier messages:\n\n"
|
|
495
|
+
f"{summary_text}\n\n"
|
|
496
|
+
f"[END COMPACTED HISTORY - Recent conversation follows]")
|
|
497
|
+
compacted_msg = make_control_message(body, MessageOrigin.COMPACTION_SUMMARY)
|
|
498
|
+
self.history.messages.clear()
|
|
499
|
+
self.history.total_tokens = 0
|
|
500
|
+
self._add_message_with_tokens(compacted_msg, _internal=True)
|
|
501
|
+
for msg in recent_messages:
|
|
502
|
+
self._add_message_with_tokens(msg, _internal=True)
|
|
503
|
+
|
|
504
|
+
def _build_static_fallback_summary(self, messages: list) -> str:
|
|
505
|
+
"""A6: deterministic, no-LLM summary from tool names / files / errors / asks."""
|
|
506
|
+
import re
|
|
507
|
+
|
|
508
|
+
files: list = []
|
|
509
|
+
errors: list = []
|
|
510
|
+
user_asks: list = []
|
|
511
|
+
tool_calls = 0
|
|
512
|
+
for msg in messages:
|
|
513
|
+
role = msg.__class__.__name__.replace("Message", "")
|
|
514
|
+
content = str(getattr(msg, "content", ""))
|
|
515
|
+
if "Tool" in role:
|
|
516
|
+
tool_calls += 1
|
|
517
|
+
for token in re.findall(r"[\w./\\-]+\.[A-Za-z0-9]{1,6}", content):
|
|
518
|
+
if token not in files:
|
|
519
|
+
files.append(token)
|
|
520
|
+
for line in content.splitlines():
|
|
521
|
+
low = line.lower()
|
|
522
|
+
if "error" in low or "exception" in low or "traceback" in low:
|
|
523
|
+
errors.append(line.strip()[:200])
|
|
524
|
+
elif role == "Human":
|
|
525
|
+
snippet = content.strip()
|
|
526
|
+
if snippet:
|
|
527
|
+
user_asks.append(snippet[:300])
|
|
528
|
+
|
|
529
|
+
files = files[:40]
|
|
530
|
+
errors = errors[:20]
|
|
531
|
+
sections = [
|
|
532
|
+
"## Active Task",
|
|
533
|
+
user_asks[0] if user_asks else "(unknown — static fallback)",
|
|
534
|
+
"## In Progress",
|
|
535
|
+
f"(static fallback after summarizer failure; {tool_calls} tool calls across {len(messages)} messages)",
|
|
536
|
+
"## Resolved Questions",
|
|
537
|
+
"(not derivable without the summarizer)",
|
|
538
|
+
"## Pending User Asks",
|
|
539
|
+
"\n".join(f"- {u}" for u in user_asks[-3:]) or "(none)",
|
|
540
|
+
"## Relevant Files & Data",
|
|
541
|
+
"\n".join(f"- {f}" for f in files) or "(none)",
|
|
542
|
+
"## Remaining Work",
|
|
543
|
+
("Errors seen:\n" + "\n".join(f"- {e}" for e in errors)) if errors else "(none recorded)",
|
|
544
|
+
]
|
|
545
|
+
return "\n".join(sections)[:_STATIC_FALLBACK_CAP]
|
|
546
|
+
|
|
547
|
+
def _build_compaction_prompt(self, messages: list, prior_summary: Optional[str] = None) -> str:
|
|
548
|
+
"""Build a STRUCTURED summarization prompt from the full middle (A1/A2/A3/A4).
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
messages: messages to summarize (the whole middle, not the last 50).
|
|
552
|
+
prior_summary: an existing compacted summary to UPDATE in place (A4).
|
|
553
|
+
"""
|
|
554
|
+
conversation_text = []
|
|
555
|
+
for msg in messages:
|
|
556
|
+
role = msg.__class__.__name__.replace("Message", "")
|
|
557
|
+
raw = str(msg.content) if hasattr(msg, 'content') else str(msg)
|
|
558
|
+
if 'Tool' in role:
|
|
559
|
+
# A2: serialize the tool output (head+tail) instead of a bare marker.
|
|
560
|
+
conversation_text.append(f"[Tool result] {_head_tail(raw, _TOOL_RESULT_CAP)}")
|
|
561
|
+
else:
|
|
562
|
+
conversation_text.append(f"{role}: {_head_tail(raw, _PER_MSG_CAP)}")
|
|
563
|
+
|
|
564
|
+
# A1: include the ENTIRE middle, oldest first.
|
|
565
|
+
conversation = "\n".join(conversation_text)
|
|
566
|
+
|
|
567
|
+
# A3: scale the target length to the volume compacted (~5 chars/word, 20% ratio).
|
|
568
|
+
total_chars = sum(len(t) for t in conversation_text)
|
|
569
|
+
budget_words = max(400, min(int(total_chars * _TAIL_TOKEN_RATIO / 5), 2400))
|
|
570
|
+
|
|
571
|
+
# A4: feed the prior summary back so it is merged/updated, not re-summarized blind.
|
|
572
|
+
prior_block = ""
|
|
573
|
+
if prior_summary:
|
|
574
|
+
prior_block = (
|
|
575
|
+
"## PRIOR SUMMARY (update this — PRESERVE all existing information, "
|
|
576
|
+
"merge in new facts, drop nothing):\n"
|
|
577
|
+
f"{prior_summary}\n\n"
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
return f"""Summarize the conversation below into a STRUCTURED running memory.
|
|
581
|
+
Fill every section; write "(none)" where empty. Preserve concrete data, IDs, file
|
|
582
|
+
paths, tool outcomes, and decisions verbatim where short.
|
|
583
|
+
|
|
584
|
+
## Active Task
|
|
585
|
+
## Goal & Constraints
|
|
586
|
+
## Completed Actions (with outcomes)
|
|
587
|
+
## In Progress
|
|
588
|
+
## Blocked / Open Questions
|
|
589
|
+
## Key Decisions
|
|
590
|
+
## Resolved Questions
|
|
591
|
+
## Pending User Asks
|
|
592
|
+
## Relevant Files & Data
|
|
593
|
+
## Remaining Work
|
|
594
|
+
|
|
595
|
+
Target length: ~{budget_words} words. Focus on WHAT was accomplished, not HOW.
|
|
596
|
+
|
|
597
|
+
TEMPORAL ANCHORING: record finished work as DONE (past tense, with its outcome) under
|
|
598
|
+
Completed Actions. Do NOT list an already-completed action under In Progress or
|
|
599
|
+
Remaining Work — after this summary replaces the raw history, anything left phrased as
|
|
600
|
+
a pending "to-do" will be RE-RUN. Only genuinely-unfinished work belongs in Remaining
|
|
601
|
+
Work.
|
|
602
|
+
|
|
603
|
+
{prior_block}Conversation to summarize:
|
|
604
|
+
{conversation}
|
|
605
|
+
|
|
606
|
+
STRUCTURED SUMMARY:"""
|
|
607
|
+
|
|
608
|
+
# (check_and_compact_if_needed removed 2026-06-29 dead-loop prune: it had ZERO
|
|
609
|
+
# callers. The live compaction policy is the hardcoded 85/95/70 thresholds in
|
|
610
|
+
# agent/core/step.py, not CompactionManager.should_compact_messages — this method
|
|
611
|
+
# was a dead alternate path that never ran.)
|