dreadnode 2.0.10__tar.gz → 2.0.12__tar.gz
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.
- {dreadnode-2.0.10 → dreadnode-2.0.12}/PKG-INFO +2 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/agent.py +163 -6
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/events.py +27 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/hooks.py +73 -79
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/api/client.py +92 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/api/models.py +37 -2
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/airt.py +14 -2
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/args.py +24 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/capability.py +64 -19
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/evaluation.py +24 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/main.py +11 -2
- dreadnode-2.0.12/dreadnode/app/cli/runtime.py +470 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/shared.py +1 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/task.py +132 -51
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/verify.sh +1 -1
- dreadnode-2.0.12/dreadnode/app/client/__init__.py +1 -0
- dreadnode-2.0.12/dreadnode/app/client/interactive.py +541 -0
- dreadnode-2.0.12/dreadnode/app/client/managed_client.py +396 -0
- dreadnode-2.0.12/dreadnode/app/client/models.py +190 -0
- dreadnode-2.0.12/dreadnode/app/client/runtime_client.py +742 -0
- dreadnode-2.0.12/dreadnode/app/client/transports.py +436 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/config.py +54 -0
- dreadnode-2.0.12/dreadnode/app/env.py +66 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/main.py +37 -14
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/print_mode.py +30 -25
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/app.py +1096 -1393
- dreadnode-2.0.12/dreadnode/app/server/auth.py +66 -0
- dreadnode-2.0.12/dreadnode/app/server/capability_manager.py +772 -0
- dreadnode-2.0.12/dreadnode/app/server/model_resolution.py +125 -0
- dreadnode-2.0.12/dreadnode/app/server/prompt_registry.py +153 -0
- dreadnode-2.0.12/dreadnode/app/server/runtime_events.py +503 -0
- dreadnode-2.0.12/dreadnode/app/server/session_hydrator.py +216 -0
- dreadnode-2.0.12/dreadnode/app/server/session_persistence.py +351 -0
- dreadnode-2.0.12/dreadnode/app/server/session_policy.py +426 -0
- dreadnode-2.0.12/dreadnode/app/server/turn_coordinator.py +183 -0
- dreadnode-2.0.12/dreadnode/app/server/websocket.py +731 -0
- dreadnode-2.0.12/dreadnode/app/server/worker_manager.py +517 -0
- dreadnode-2.0.12/dreadnode/app/tui/app.py +3948 -0
- dreadnode-2.0.12/dreadnode/app/tui/auth_flow.py +195 -0
- dreadnode-2.0.12/dreadnode/app/tui/capabilities_manager.py +388 -0
- dreadnode-2.0.12/dreadnode/app/tui/command_dispatcher.py +754 -0
- dreadnode-2.0.12/dreadnode/app/tui/commands.py +79 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/connection.py +33 -30
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/dreadnode.tcss +83 -333
- dreadnode-2.0.12/dreadnode/app/tui/error_handler.py +175 -0
- dreadnode-2.0.12/dreadnode/app/tui/model_manager.py +704 -0
- dreadnode-2.0.12/dreadnode/app/tui/profile_manager.py +1127 -0
- dreadnode-2.0.12/dreadnode/app/tui/screen_router.py +282 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/__init__.py +2 -2
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/auth.py +11 -3
- dreadnode-2.0.12/dreadnode/app/tui/screens/base.py +257 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/capabilities.py +264 -68
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/runtimes.py +136 -62
- dreadnode-2.0.12/dreadnode/app/tui/screens/services.py +700 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/sessions.py +122 -56
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/workspaces.py +44 -47
- dreadnode-2.0.12/dreadnode/app/tui/sessions_manager.py +1625 -0
- dreadnode-2.0.12/dreadnode/app/tui/tool_format.py +311 -0
- dreadnode-2.0.12/dreadnode/app/tui/turn_coordinator.py +642 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/turn_lifecycle.py +25 -1
- dreadnode-2.0.12/dreadnode/app/tui/turn_reducer.py +348 -0
- dreadnode-2.0.12/dreadnode/app/tui/turn_state_phase.py +62 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/__init__.py +0 -2
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/agent_suggester.py +1 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/composer.py +3 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/context_bar.py +14 -0
- dreadnode-2.0.12/dreadnode/app/tui/widgets/conversation.py +476 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/help_panel.py +2 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/tool.py +1 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/tools_dialog.py +7 -15
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/welcome.py +1 -0
- dreadnode-2.0.12/dreadnode/app/tui/wire_events.py +464 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-runtime-reference/SKILL.md +3 -5
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/__init__.py +2 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/capability.py +72 -1
- dreadnode-2.0.12/dreadnode/capabilities/flags.py +299 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/loader.py +438 -11
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/sync.py +1 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/types.py +32 -1
- dreadnode-2.0.12/dreadnode/capabilities/worker.py +268 -0
- dreadnode-2.0.12/dreadnode/capabilities/worker_runner.py +588 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/exceptions.py +4 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/litellm_.py +66 -1
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/jobs.py +1 -2
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/manifest.py +3 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/oci.py +48 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/task_validation.py +97 -4
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/storage.py +132 -6
- dreadnode-2.0.12/dreadnode/tracing/exporter.py +66 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/pyproject.toml +2 -1
- dreadnode-2.0.10/dreadnode/app/cli/runtime.py +0 -55
- dreadnode-2.0.10/dreadnode/app/server/auth.py +0 -53
- dreadnode-2.0.10/dreadnode/app/tui/app.py +0 -5136
- dreadnode-2.0.10/dreadnode/app/tui/client.py +0 -1010
- dreadnode-2.0.10/dreadnode/app/tui/commands.py +0 -505
- dreadnode-2.0.10/dreadnode/app/tui/event_contract.py +0 -333
- dreadnode-2.0.10/dreadnode/app/tui/screens/base.py +0 -108
- dreadnode-2.0.10/dreadnode/app/tui/screens/mcp.py +0 -493
- dreadnode-2.0.10/dreadnode/app/tui/turn_reducer.py +0 -294
- dreadnode-2.0.10/dreadnode/app/tui/widgets/conversation.py +0 -332
- dreadnode-2.0.10/dreadnode/skills/__init__.py +0 -27
- dreadnode-2.0.10/dreadnode/tracing/exporter.py +0 -23
- {dreadnode-2.0.10 → dreadnode-2.0.12}/.gitignore +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/LICENSE +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/README.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/__main__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/exceptions.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/format.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/auth.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/client.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/config.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/server.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/reactions.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/skills.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/stopping.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/subagent.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/tools.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/trajectory.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/aggregator.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/classifier.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/compliance.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/engine.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/recommendations.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/types.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/assessment.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/autodan_turbo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/beast.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/atlas.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/nist.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/owasp.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/owasp_agentic.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/saif.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/constants.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/crescendo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/assets/audio/adversarial_query.mp3 +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/assets/image/bomb.jpg +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/assets/image/meth.png +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/prompts/adversarial_benchmark_subset.csv +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/prompts/ai_safety.csv +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/data_exfiltration.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/goal_hijacking.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/memory_poisoning.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/privilege_escalation.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/rce.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/scope_creep.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/tool_chaining.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/tool_selection_safety.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/unbounded_agency.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/web_chatbot_security.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_1.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_2.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_3.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_4.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_5.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/deep_inception.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/drattack.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/events.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/goat.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/gptfuzzer.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/image.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/multimodal.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/pair.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/prompt.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/rainbow.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/renellm.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/json_report.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/llm_summary.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/markdown.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/tap.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/api/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/dataset.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/model.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/optimize.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/sandbox.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/challenge/Dockerfile +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/docker-compose.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/provision.sh +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/solution.sh +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/task-remote.yaml.tmpl +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/task.yaml.tmpl +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/teardown.sh +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/train.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/worlds.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/model_catalog.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/prompt.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/utils.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/model_variants.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/runtime_cache.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/capability_docs.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/connection_error.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/console.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/environments.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/evaluations.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/model_picker.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/models.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/raw_spans.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/sandboxes.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/secrets.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/theme_showcase.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/traces.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/spans_reader.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/theme.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/update_check.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/agent_dialog.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/flash.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/header_bar.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/mention_overlay.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/message_queue.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/overlay_mixin.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/permission_prompt.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/profile_dialog.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/prompt_info.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/session_sidebar.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/skills_dialog.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/slash_overlay.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/status_bar.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/throbber.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/tool_progress.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/whoami.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/agents/dreadnode.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/capability.yaml +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/SKILL.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/capability-components.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/capability-improvement.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/runtime-default-capability.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/dreadnode-cli/SKILL.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/dreadnode-cli/references/command-groups.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/dreadnode-cli/references/tui-crosswalk.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/system-prompt.md +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/tool_rules.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/conditions.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/discovery.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/environment.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/execution.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/hook.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/judge.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/load.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/log.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/config.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/context.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/hydrate.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/introspect.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/metric.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/object.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/scorer.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/serialization.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/stopping.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/task.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/transforms.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/audio.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/base.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/common.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/image.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/object_3d.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/table.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/text.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/video.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/util.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/dataset.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/hf.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/local.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/console.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/evaluation.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/events.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/format.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/result.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/sample.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/caching.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/chat.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/data.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/exceptions.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/base.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/http.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/transformers_.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/vllm_.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/message.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/models.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/parsing.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/tokenizer/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/tokenizer/base.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/tokenizer/transformers_.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/utils.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/hf.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/local.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/model.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/adapters/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/adapters/agent.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/adapters/stack.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/api.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/backends/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/backends/base.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/backends/gepa.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/collectors.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/config.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/console.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/events.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/format.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/result.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/sampler.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/sampling.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/search.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/stopping.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/study.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/trial.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/loader.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/package.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/py.typed +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/boundary.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/fuzzing.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/graph.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/grid.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/image.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/mapelites.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/optuna.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/random.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/registry.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/strategy.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/advanced_jailbreak_detection.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/agent_security.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/agentic.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/agentic_workflow.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/classification.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/consistency.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/contains.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/cosine_sim.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/credentials.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/crucible.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/documentation_security.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/exfiltration_detection.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/format.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/harm.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/ide_security.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/image.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/json.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/judge.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/length.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/lexical.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/mcp_security.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/memorization.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/multi_agent_security.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/pii.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/prompt_leak.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/readability.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/reasoning_security.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/sentiment.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/similarity.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/providers.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/session_store.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/_ripgrep.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/apply_patch.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/dreadnode_cli.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/editing.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/execute.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/fetch.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/glob.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/grep.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/interaction.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/ls.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/memory.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/read.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/report.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/task.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/think.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/todo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/trajectory_search.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/web_search.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/write.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/constants.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/convert.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/exporters.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/span.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/spans.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/trace_converter.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/base.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/dpo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/_common.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/rl.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/sft.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/worlds.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/events.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/grpo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/jobs.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ppo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/prime.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/async_trainer.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/config.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/coordinator.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/distributed.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/dpo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/experience.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/fsdp2_learner.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/inference.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/learner.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/multi_turn.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/ppo.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/reward_model.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/rollout_env.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/rollout_worker.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/sft.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/trainer.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/recipes.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/aggregator.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/functions.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/scorer_bridge.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/shaping.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/types.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/adapters.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/orchestrator.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/types.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/worlds.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/serving/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/serving/vllm_client.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/sft.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/config.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/data.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/renderer.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/rl.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/trainer.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker_sft.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/utils.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/__init__.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/advanced_jailbreak.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/adversarial_suffix.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/agent_skill.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/agentic_workflow.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/audio.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/browser_agent_attacks.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/cipher.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/constitutional.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/document.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/documentation_poison.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/encoding.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/exfiltration.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/flip_attack.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/guardrail_bypass.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/ide_injection.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/image.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/injection.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/json_tools.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/language.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/logic_bomb.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/mcp_attacks.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/multi_agent_attacks.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/persuasion.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/perturbation.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/pii_extraction.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/pythonic_tools.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/rag_poisoning.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/reasoning_attacks.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/refine.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/response_steering.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/stylistic.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/substitution.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/swap.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/system_prompt_extraction.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/text.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/video.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/xml_tools.py +0 -0
- {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dreadnode
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.12
|
|
4
4
|
Summary: Dreadnode SDK
|
|
5
5
|
Project-URL: Homepage, https://dreadnode.io
|
|
6
6
|
Project-URL: Documentation, https://docs.dreadnode.io
|
|
@@ -10,6 +10,7 @@ Requires-Python: <3.14,>=3.11
|
|
|
10
10
|
Requires-Dist: aiofiles<25.0.0,>=24.1.0
|
|
11
11
|
Requires-Dist: art<7.0.0,>=6.5
|
|
12
12
|
Requires-Dist: coolname<3.0.0,>=2.2.0
|
|
13
|
+
Requires-Dist: croniter<7.0.0,>=6.0.0
|
|
13
14
|
Requires-Dist: cyclopts>=4.2.0
|
|
14
15
|
Requires-Dist: datasets>=4.5.0
|
|
15
16
|
Requires-Dist: fastapi>=0.115.0
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import inspect
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
3
6
|
import typing as t
|
|
4
7
|
from contextlib import AsyncExitStack, asynccontextmanager, suppress
|
|
5
8
|
from copy import deepcopy
|
|
@@ -30,6 +33,7 @@ from dreadnode.agents.events import (
|
|
|
30
33
|
GenerationContent,
|
|
31
34
|
GenerationEnd,
|
|
32
35
|
GenerationError,
|
|
36
|
+
GenerationRetry,
|
|
33
37
|
GenerationStart,
|
|
34
38
|
GenerationStep,
|
|
35
39
|
ReactStep,
|
|
@@ -80,6 +84,45 @@ def _raise_exception(error: BaseException) -> t.NoReturn:
|
|
|
80
84
|
raise error
|
|
81
85
|
|
|
82
86
|
|
|
87
|
+
_TRANSIENT_LITELLM_EXCEPTION_NAMES: tuple[str, ...] = (
|
|
88
|
+
"RateLimitError",
|
|
89
|
+
"Timeout",
|
|
90
|
+
"APIConnectionError",
|
|
91
|
+
"APIConnectionTimeoutError",
|
|
92
|
+
"ServiceUnavailableError",
|
|
93
|
+
"InternalServerError",
|
|
94
|
+
"BadGatewayError",
|
|
95
|
+
"APIError",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _is_transient_api_error(error: BaseException) -> bool:
|
|
100
|
+
"""Classify an error as a transient LLM API failure worth retrying.
|
|
101
|
+
|
|
102
|
+
Matches an explicit allow-list of ``litellm.exceptions`` classes that
|
|
103
|
+
represent recoverable conditions: rate limits, timeouts, connection
|
|
104
|
+
failures, and 5xx responses. Notably **does not** match
|
|
105
|
+
``BadRequestError``, ``AuthenticationError``,
|
|
106
|
+
``ContextWindowExceededError`` (handled by overflow recovery), or other
|
|
107
|
+
deterministic client errors.
|
|
108
|
+
|
|
109
|
+
The allow-list is walked dynamically because ``litellm.exceptions`` does
|
|
110
|
+
not expose a single common ancestor for its transient exceptions.
|
|
111
|
+
Returns ``False`` if ``litellm`` is not importable.
|
|
112
|
+
"""
|
|
113
|
+
with contextlib.suppress(ImportError):
|
|
114
|
+
import litellm.exceptions as _litellm_exc
|
|
115
|
+
|
|
116
|
+
classes = tuple(
|
|
117
|
+
cls
|
|
118
|
+
for name in _TRANSIENT_LITELLM_EXCEPTION_NAMES
|
|
119
|
+
if (cls := getattr(_litellm_exc, name, None)) is not None
|
|
120
|
+
)
|
|
121
|
+
if classes and isinstance(error, classes):
|
|
122
|
+
return True
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
83
126
|
class Agent(Executor[AgentEvent, Trajectory]):
|
|
84
127
|
"""
|
|
85
128
|
Agent abstraction for applying tools, event logic, and message state to LLM generation.
|
|
@@ -128,6 +171,14 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
128
171
|
"""Extra parameters merged into GenerateParams for every generation (e.g. thinking config)."""
|
|
129
172
|
working_dir: Path | None = Config(default=None)
|
|
130
173
|
"""Working directory used for tool output offloading and other IO."""
|
|
174
|
+
backoff_max_tries: int = Config(default=8, ge=0)
|
|
175
|
+
"""Maximum retries on transient LLM API errors per step. ``0`` disables retry."""
|
|
176
|
+
backoff_max_time: float = Config(default=300.0, ge=0)
|
|
177
|
+
"""Maximum total seconds to spend retrying transient LLM API errors per step."""
|
|
178
|
+
backoff_base_factor: float = Config(default=1.0, ge=0)
|
|
179
|
+
"""Base factor for exponential backoff: wait = base_factor * 2 ** (attempt - 1)."""
|
|
180
|
+
backoff_jitter: bool = Config(default=True)
|
|
181
|
+
"""Whether to add up to ``backoff_base_factor`` seconds of random jitter to each wait."""
|
|
131
182
|
|
|
132
183
|
# Private state
|
|
133
184
|
_generator: Generator | None = PrivateAttr(None, init=False)
|
|
@@ -381,6 +432,49 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
381
432
|
|
|
382
433
|
raise winning_reaction
|
|
383
434
|
|
|
435
|
+
def _try_backoff(
|
|
436
|
+
self,
|
|
437
|
+
error: BaseException,
|
|
438
|
+
*,
|
|
439
|
+
tries: int,
|
|
440
|
+
start_time: float,
|
|
441
|
+
) -> float | None:
|
|
442
|
+
"""Compute a retry sleep for a transient LLM API error.
|
|
443
|
+
|
|
444
|
+
Returns the sleep duration in seconds if the caller should retry, or
|
|
445
|
+
``None`` if the error is not transient, tries are exhausted, or the
|
|
446
|
+
per-step time budget has been spent. Caller owns sleeping and emitting
|
|
447
|
+
the ``GenerationRetry`` event. Backoff is agent-owned at the error
|
|
448
|
+
site, mirroring ``_try_overflow_recovery`` — no step is consumed and
|
|
449
|
+
no hook indirection is involved.
|
|
450
|
+
"""
|
|
451
|
+
if self.backoff_max_tries <= 0:
|
|
452
|
+
return None
|
|
453
|
+
|
|
454
|
+
if not _is_transient_api_error(error):
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
if tries >= self.backoff_max_tries:
|
|
458
|
+
logger.warning("Backoff aborted: max tries ({}) exceeded.", self.backoff_max_tries)
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
remaining = self.backoff_max_time - (time.monotonic() - start_time)
|
|
462
|
+
if remaining <= 0:
|
|
463
|
+
logger.warning("Backoff aborted: max time ({:.2f}s) exceeded.", self.backoff_max_time)
|
|
464
|
+
return None
|
|
465
|
+
|
|
466
|
+
seconds = self.backoff_base_factor * (2**tries)
|
|
467
|
+
if self.backoff_jitter:
|
|
468
|
+
seconds += random.uniform(0, self.backoff_base_factor)
|
|
469
|
+
if seconds > remaining:
|
|
470
|
+
logger.warning(
|
|
471
|
+
"Backoff aborted: next sleep ({:.2f}s) would exceed remaining budget ({:.2f}s).",
|
|
472
|
+
seconds,
|
|
473
|
+
remaining,
|
|
474
|
+
)
|
|
475
|
+
return None
|
|
476
|
+
return seconds
|
|
477
|
+
|
|
384
478
|
async def _try_overflow_recovery(
|
|
385
479
|
self,
|
|
386
480
|
error: BaseException,
|
|
@@ -393,6 +487,7 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
393
487
|
than via hook indirection.
|
|
394
488
|
"""
|
|
395
489
|
from dreadnode.agents.hooks import (
|
|
490
|
+
_get_model_context_budget,
|
|
396
491
|
_is_context_length_error,
|
|
397
492
|
find_summarization_boundary,
|
|
398
493
|
summarize_conversation,
|
|
@@ -407,19 +502,38 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
407
502
|
work = list(messages)
|
|
408
503
|
system_message: Message | None = work.pop(0) if work and work[0].role == "system" else None
|
|
409
504
|
|
|
410
|
-
|
|
505
|
+
summarizer = self._generator
|
|
506
|
+
if summarizer is None:
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
# Cap summarizer input at ~60% of the model's token budget (~3 chars/token,
|
|
510
|
+
# conservative to prefer over-truncating on code/JSON-heavy content).
|
|
511
|
+
# This keeps the recovery call itself from overflowing the same provider
|
|
512
|
+
# context that triggered recovery.
|
|
513
|
+
budget_tokens = _get_model_context_budget(summarizer)
|
|
514
|
+
max_summarize_chars = int(budget_tokens * 0.6 * 3)
|
|
515
|
+
|
|
516
|
+
boundary = find_summarization_boundary(
|
|
517
|
+
work,
|
|
518
|
+
min_messages_to_keep=10,
|
|
519
|
+
max_summarize_chars=max_summarize_chars,
|
|
520
|
+
)
|
|
411
521
|
if boundary == 0:
|
|
522
|
+
logger.info(
|
|
523
|
+
"Overflow recovery: no safe summarization window fits summarizer budget "
|
|
524
|
+
"(budget_tokens={}, max_chars={})",
|
|
525
|
+
budget_tokens,
|
|
526
|
+
max_summarize_chars,
|
|
527
|
+
)
|
|
412
528
|
return None
|
|
413
529
|
|
|
414
530
|
to_summarize = work[:boundary]
|
|
415
531
|
to_keep = work[boundary:]
|
|
416
532
|
|
|
417
|
-
summarizer = self._generator
|
|
418
|
-
if summarizer is None:
|
|
419
|
-
return None
|
|
420
|
-
|
|
421
533
|
logger.info(
|
|
422
|
-
|
|
534
|
+
"Overflow recovery: summarizing {} messages, keeping {}",
|
|
535
|
+
len(to_summarize),
|
|
536
|
+
len(to_keep),
|
|
423
537
|
)
|
|
424
538
|
|
|
425
539
|
try:
|
|
@@ -628,6 +742,43 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
628
742
|
|
|
629
743
|
step_chat = await self._generate(messages)
|
|
630
744
|
|
|
745
|
+
# In-place transient-error backoff — rate limits and
|
|
746
|
+
# other litellm.APIError failures get retried at the
|
|
747
|
+
# error site with exponential backoff. No step budget
|
|
748
|
+
# consumed; clients observe GenerationRetry events
|
|
749
|
+
# rather than a spurious terminal GenerationError.
|
|
750
|
+
backoff_tries = 0
|
|
751
|
+
backoff_started = time.monotonic()
|
|
752
|
+
while step_chat.failed and step_chat.error:
|
|
753
|
+
wait = self._try_backoff(
|
|
754
|
+
step_chat.error,
|
|
755
|
+
tries=backoff_tries,
|
|
756
|
+
start_time=backoff_started,
|
|
757
|
+
)
|
|
758
|
+
if wait is None:
|
|
759
|
+
break
|
|
760
|
+
backoff_tries += 1
|
|
761
|
+
logger.warning(
|
|
762
|
+
"Backing off {:.2f}s (try {}/{})",
|
|
763
|
+
wait,
|
|
764
|
+
backoff_tries,
|
|
765
|
+
self.backoff_max_tries,
|
|
766
|
+
)
|
|
767
|
+
retry_event = GenerationRetry(
|
|
768
|
+
agent_id=self.agent_id,
|
|
769
|
+
agent_name=self.name,
|
|
770
|
+
step=step_count,
|
|
771
|
+
attempt=backoff_tries,
|
|
772
|
+
max_attempts=self.backoff_max_tries,
|
|
773
|
+
wait_seconds=wait,
|
|
774
|
+
error_type=type(step_chat.error).__name__,
|
|
775
|
+
error_message=str(step_chat.error),
|
|
776
|
+
)
|
|
777
|
+
async for event in self._dispatch(retry_event):
|
|
778
|
+
yield event
|
|
779
|
+
await asyncio.sleep(wait)
|
|
780
|
+
step_chat = await self._generate(messages)
|
|
781
|
+
|
|
631
782
|
# In-place overflow recovery — at most once per step,
|
|
632
783
|
# no step budget consumed, preserves step_count for
|
|
633
784
|
# user_message inclusion in trajectory.
|
|
@@ -650,6 +801,12 @@ class Agent(Executor[AgentEvent, Trajectory]):
|
|
|
650
801
|
step_chat = await self._generate(messages)
|
|
651
802
|
|
|
652
803
|
if step_chat.failed and step_chat.error:
|
|
804
|
+
from dreadnode.agents.hooks import _describe_generation_error
|
|
805
|
+
|
|
806
|
+
logger.error(
|
|
807
|
+
"Generation step failed: {}",
|
|
808
|
+
_describe_generation_error(step_chat.error),
|
|
809
|
+
)
|
|
653
810
|
error_event = GenerationError(
|
|
654
811
|
agent_id=self.agent_id,
|
|
655
812
|
agent_name=self.name,
|
|
@@ -311,6 +311,32 @@ class CompactionEvent(AgentEvent):
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
|
|
314
|
+
class GenerationRetry(AgentEvent):
|
|
315
|
+
"""Lifecycle event: the agent is about to sleep and retry a failed generation.
|
|
316
|
+
|
|
317
|
+
Emitted by the agent loop when a transient LLM API error (rate limit, etc.)
|
|
318
|
+
is recovered in place via ``Agent._try_backoff``. This is a lifecycle signal
|
|
319
|
+
only — it does not consume a step or land in the trajectory.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
step: int
|
|
323
|
+
attempt: int
|
|
324
|
+
max_attempts: int
|
|
325
|
+
wait_seconds: float
|
|
326
|
+
error_type: str
|
|
327
|
+
error_message: str
|
|
328
|
+
|
|
329
|
+
def _get_data(self) -> dict[str, t.Any]:
|
|
330
|
+
return {
|
|
331
|
+
"step": self.step,
|
|
332
|
+
"attempt": self.attempt,
|
|
333
|
+
"max_attempts": self.max_attempts,
|
|
334
|
+
"wait_seconds": self.wait_seconds,
|
|
335
|
+
"error_type": self.error_type,
|
|
336
|
+
"error_message": self.error_message,
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
314
340
|
class AgentStalled(AgentEvent):
|
|
315
341
|
"""Event: The agent is stalled and there are no tool calls, or stop condition).
|
|
316
342
|
|
|
@@ -1017,6 +1043,7 @@ EVENT_TYPES: dict[str, type[AgentEvent]] = {
|
|
|
1017
1043
|
"GenerationStep": GenerationStep,
|
|
1018
1044
|
"GenerationContent": GenerationContent,
|
|
1019
1045
|
"GenerationError": GenerationError,
|
|
1046
|
+
"GenerationRetry": GenerationRetry,
|
|
1020
1047
|
"ReactStep": ReactStep,
|
|
1021
1048
|
"UserInputRequired": UserInputRequired,
|
|
1022
1049
|
"Heartbeat": Heartbeat,
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Optional agent hooks: tool metrics and conversation summarization.
|
|
2
2
|
|
|
3
|
-
These hooks are
|
|
3
|
+
These hooks are opt-in — users register them explicitly on an ``Agent`` via
|
|
4
|
+
the ``hooks=`` constructor argument. Transient-error backoff is handled inline
|
|
5
|
+
by the agent loop (see ``Agent._try_backoff``) and is not a hook.
|
|
4
6
|
"""
|
|
5
7
|
|
|
6
|
-
import asyncio
|
|
7
8
|
import contextlib
|
|
8
|
-
import random
|
|
9
|
-
import time
|
|
10
9
|
import typing as t
|
|
11
10
|
from dataclasses import dataclass
|
|
12
11
|
|
|
13
|
-
from loguru import logger
|
|
14
|
-
|
|
15
12
|
from dreadnode.agents.events import (
|
|
16
|
-
AgentError,
|
|
17
13
|
AgentEvent,
|
|
18
14
|
AgentStep,
|
|
19
15
|
ToolEnd,
|
|
@@ -28,73 +24,6 @@ if t.TYPE_CHECKING:
|
|
|
28
24
|
from datetime import datetime
|
|
29
25
|
|
|
30
26
|
|
|
31
|
-
# =============================================================================
|
|
32
|
-
# Backoff
|
|
33
|
-
# =============================================================================
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _make_backoff_hook(
|
|
37
|
-
exception_types: tuple[type[Exception], ...],
|
|
38
|
-
*,
|
|
39
|
-
max_tries: int = 8,
|
|
40
|
-
max_time: float = 300.0,
|
|
41
|
-
base_factor: float = 1.0,
|
|
42
|
-
jitter: bool = True,
|
|
43
|
-
):
|
|
44
|
-
"""Create a backoff hook for specific exception types."""
|
|
45
|
-
tries = 0
|
|
46
|
-
start_time: float | None = None
|
|
47
|
-
last_step_seen = -1
|
|
48
|
-
|
|
49
|
-
@hook(AgentEvent)
|
|
50
|
-
async def backoff_hook(event: AgentEvent) -> Reaction | None:
|
|
51
|
-
nonlocal tries, start_time, last_step_seen
|
|
52
|
-
|
|
53
|
-
if isinstance(event, AgentStep):
|
|
54
|
-
if event.step > last_step_seen:
|
|
55
|
-
tries = 0
|
|
56
|
-
start_time = None
|
|
57
|
-
last_step_seen = event.step
|
|
58
|
-
return None
|
|
59
|
-
|
|
60
|
-
if not isinstance(event, AgentError) or not isinstance(event.error, exception_types):
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
if start_time is None:
|
|
64
|
-
start_time = time.monotonic()
|
|
65
|
-
|
|
66
|
-
if tries >= max_tries:
|
|
67
|
-
logger.warning(f"Backoff aborted: max tries ({max_tries}) exceeded.")
|
|
68
|
-
return None
|
|
69
|
-
|
|
70
|
-
if (time.monotonic() - start_time) >= max_time:
|
|
71
|
-
logger.warning(f"Backoff aborted: max time ({max_time:.2f}s) exceeded.")
|
|
72
|
-
return None
|
|
73
|
-
|
|
74
|
-
tries += 1
|
|
75
|
-
seconds = base_factor * (2 ** (tries - 1))
|
|
76
|
-
if jitter:
|
|
77
|
-
seconds += random.uniform(0, base_factor)
|
|
78
|
-
|
|
79
|
-
logger.warning(f"Backing off {seconds:.2f}s (try {tries}/{max_tries})")
|
|
80
|
-
await asyncio.sleep(seconds)
|
|
81
|
-
return Retry()
|
|
82
|
-
|
|
83
|
-
return backoff_hook
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# Pre-instantiated for common LLM rate limit errors
|
|
87
|
-
backoff_on_ratelimit: Hook | None = None
|
|
88
|
-
try:
|
|
89
|
-
import litellm.exceptions
|
|
90
|
-
|
|
91
|
-
backoff_on_ratelimit = _make_backoff_hook(
|
|
92
|
-
(litellm.exceptions.RateLimitError, litellm.exceptions.APIError),
|
|
93
|
-
)
|
|
94
|
-
except ImportError:
|
|
95
|
-
pass
|
|
96
|
-
|
|
97
|
-
|
|
98
27
|
# =============================================================================
|
|
99
28
|
# Tool Metrics
|
|
100
29
|
# =============================================================================
|
|
@@ -151,6 +80,9 @@ CONTEXT_LENGTH_ERROR_PATTERNS = [
|
|
|
151
80
|
"token limit",
|
|
152
81
|
"maximum context length",
|
|
153
82
|
"is too long",
|
|
83
|
+
"chunk too big",
|
|
84
|
+
"prompt is too long",
|
|
85
|
+
"too many tokens",
|
|
154
86
|
]
|
|
155
87
|
|
|
156
88
|
|
|
@@ -264,32 +196,94 @@ def _is_context_length_error(error: BaseException) -> bool:
|
|
|
264
196
|
return any(pattern in error_str for pattern in CONTEXT_LENGTH_ERROR_PATTERNS)
|
|
265
197
|
|
|
266
198
|
|
|
199
|
+
def _describe_generation_error(error: BaseException) -> dict[str, t.Any]:
|
|
200
|
+
"""Best-effort structured dump of a generation error for diagnostic logs.
|
|
201
|
+
|
|
202
|
+
Returns a dict of litellm-known attributes when present. Never raises:
|
|
203
|
+
attribute access that fails is silently omitted, so callers can log the
|
|
204
|
+
result safely even for hostile or malformed exception objects.
|
|
205
|
+
|
|
206
|
+
Intended for one-per-failure diagnostic logging at the generation error
|
|
207
|
+
site. Presence flags are included for fields whose *content* is not
|
|
208
|
+
itself safe to log (raw bodies may be arbitrary length); they tell a
|
|
209
|
+
future debugger whether structured data exists to look at without
|
|
210
|
+
dumping it into every log line.
|
|
211
|
+
"""
|
|
212
|
+
fields: dict[str, t.Any] = {"type": type(error).__name__}
|
|
213
|
+
|
|
214
|
+
with contextlib.suppress(Exception):
|
|
215
|
+
msg = str(error)
|
|
216
|
+
if msg:
|
|
217
|
+
fields["message"] = msg if len(msg) <= 500 else msg[:500] + "...<truncated>"
|
|
218
|
+
|
|
219
|
+
for attr in ("status_code", "llm_provider", "model", "request_id"):
|
|
220
|
+
with contextlib.suppress(Exception):
|
|
221
|
+
val = getattr(error, attr, None)
|
|
222
|
+
if val is not None:
|
|
223
|
+
fields[attr] = val
|
|
224
|
+
|
|
225
|
+
with contextlib.suppress(Exception):
|
|
226
|
+
fields["has_body"] = bool(getattr(error, "body", None))
|
|
227
|
+
|
|
228
|
+
with contextlib.suppress(Exception):
|
|
229
|
+
resp = getattr(error, "response", None)
|
|
230
|
+
if resp is not None:
|
|
231
|
+
text = getattr(resp, "text", "") or ""
|
|
232
|
+
fields["has_response_text"] = bool(text)
|
|
233
|
+
|
|
234
|
+
return fields
|
|
235
|
+
|
|
236
|
+
|
|
267
237
|
def find_summarization_boundary(
|
|
268
238
|
messages: list[Message],
|
|
269
239
|
min_messages_to_keep: int = 10,
|
|
240
|
+
max_summarize_chars: int | None = None,
|
|
270
241
|
) -> int:
|
|
271
242
|
"""Find a clean message boundary for summarization.
|
|
272
243
|
|
|
273
|
-
Walks messages from the start and
|
|
244
|
+
Walks messages from the start and enumerates every safe split point that
|
|
274
245
|
leaves at least ``min_messages_to_keep`` messages in the "keep" portion.
|
|
275
246
|
A safe boundary is after a simple assistant message (no tool calls) —
|
|
276
247
|
this is the natural end of a complete conversational turn.
|
|
277
248
|
|
|
249
|
+
When ``max_summarize_chars`` is provided, returns the largest safe split
|
|
250
|
+
whose cumulative ``len(str(message))`` stays within the cap. This keeps
|
|
251
|
+
the summarizer call from overflowing the same provider context that
|
|
252
|
+
triggered recovery. ``str(message)`` is exactly what the summarizer
|
|
253
|
+
receives (see ``Agent._try_overflow_recovery``) so the cap and the actual
|
|
254
|
+
serialized input measure the same string — including elision of image
|
|
255
|
+
URLs (``ContentImageUrl.__str__``) and tool-call arguments
|
|
256
|
+
(``ToolCall.__str__``).
|
|
257
|
+
|
|
278
258
|
Returns:
|
|
279
259
|
Index splitting ``messages[:boundary]`` (to summarize) from
|
|
280
260
|
``messages[boundary:]`` (to keep). Returns ``0`` when no valid
|
|
281
261
|
boundary exists.
|
|
282
262
|
"""
|
|
283
|
-
|
|
263
|
+
# Enumerate every safe boundary with its cumulative serialized char count.
|
|
264
|
+
# (0, 0) is always a valid "no compaction" candidate.
|
|
265
|
+
candidates: list[tuple[int, int]] = [(0, 0)]
|
|
266
|
+
running_chars = 0
|
|
284
267
|
for i, message in enumerate(messages):
|
|
285
268
|
if len(messages) - i <= min_messages_to_keep:
|
|
286
269
|
break
|
|
270
|
+
running_chars += len(str(message))
|
|
287
271
|
is_simple_assistant = message.role == "assistant" and not getattr(
|
|
288
272
|
message, "tool_calls", None
|
|
289
273
|
)
|
|
290
274
|
if is_simple_assistant:
|
|
291
|
-
|
|
292
|
-
|
|
275
|
+
candidates.append((i + 1, running_chars))
|
|
276
|
+
|
|
277
|
+
if max_summarize_chars is None:
|
|
278
|
+
return candidates[-1][0]
|
|
279
|
+
|
|
280
|
+
# Cumulative sizes are monotonic in boundary order, so the largest
|
|
281
|
+
# boundary whose size fits the cap is the best match. Walk candidates
|
|
282
|
+
# from latest to earliest and return the first that fits.
|
|
283
|
+
for boundary, size in reversed(candidates):
|
|
284
|
+
if size <= max_summarize_chars:
|
|
285
|
+
return boundary
|
|
286
|
+
return 0
|
|
293
287
|
|
|
294
288
|
|
|
295
289
|
def _get_model_context_budget(model_or_generator: "str | Generator | None") -> int:
|
|
@@ -36,6 +36,7 @@ from dreadnode.app.api.models import (
|
|
|
36
36
|
UserSecretsList,
|
|
37
37
|
Workspace,
|
|
38
38
|
)
|
|
39
|
+
from dreadnode.core.exceptions import InsufficientCreditsError
|
|
39
40
|
from dreadnode.version import VERSION
|
|
40
41
|
|
|
41
42
|
|
|
@@ -109,6 +110,11 @@ class ApiClient:
|
|
|
109
110
|
else:
|
|
110
111
|
return f"{response.status_code}: {detail}"
|
|
111
112
|
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _is_credit_related_429(detail: str) -> bool:
|
|
115
|
+
lowered = detail.lower()
|
|
116
|
+
return "credit" in lowered
|
|
117
|
+
|
|
112
118
|
def _request(
|
|
113
119
|
self,
|
|
114
120
|
method: str,
|
|
@@ -141,6 +147,11 @@ class ApiClient:
|
|
|
141
147
|
response = self._request(method, path, params, json_data, data, headers)
|
|
142
148
|
if response.status_code == 401:
|
|
143
149
|
raise AuthenticationError(self._get_error_message(response))
|
|
150
|
+
if response.status_code == 429:
|
|
151
|
+
error_message = self._get_error_message(response)
|
|
152
|
+
detail = error_message.split(": ", 1)[1] if ": " in error_message else error_message
|
|
153
|
+
if self._is_credit_related_429(detail):
|
|
154
|
+
raise InsufficientCreditsError(detail)
|
|
144
155
|
try:
|
|
145
156
|
response.raise_for_status()
|
|
146
157
|
except httpx.HTTPStatusError as e:
|
|
@@ -1086,6 +1097,64 @@ class ApiClient:
|
|
|
1086
1097
|
)
|
|
1087
1098
|
return t.cast("dict[str, t.Any]", response.json())
|
|
1088
1099
|
|
|
1100
|
+
def create_runtime(
|
|
1101
|
+
self,
|
|
1102
|
+
org: str,
|
|
1103
|
+
workspace: str,
|
|
1104
|
+
project: str | None = None,
|
|
1105
|
+
*,
|
|
1106
|
+
key: str | None = None,
|
|
1107
|
+
name: str | None = None,
|
|
1108
|
+
description: str | None = None,
|
|
1109
|
+
config: dict[str, t.Any] | None = None,
|
|
1110
|
+
) -> dict[str, t.Any]:
|
|
1111
|
+
"""POST /org/{org}/ws/{workspace}/runtimes - Ensure a runtime exists."""
|
|
1112
|
+
payload: dict[str, t.Any] = {}
|
|
1113
|
+
if project is not None:
|
|
1114
|
+
payload["project"] = project
|
|
1115
|
+
if key is not None:
|
|
1116
|
+
payload["key"] = key
|
|
1117
|
+
if name is not None:
|
|
1118
|
+
payload["name"] = name
|
|
1119
|
+
if description is not None:
|
|
1120
|
+
payload["description"] = description
|
|
1121
|
+
if config is not None:
|
|
1122
|
+
payload["config"] = config
|
|
1123
|
+
response = self.request(
|
|
1124
|
+
"POST",
|
|
1125
|
+
f"/org/{org}/ws/{workspace}/runtimes",
|
|
1126
|
+
json_data=payload,
|
|
1127
|
+
)
|
|
1128
|
+
return t.cast("dict[str, t.Any]", response.json())
|
|
1129
|
+
|
|
1130
|
+
def get_runtime_config(
|
|
1131
|
+
self,
|
|
1132
|
+
org: str,
|
|
1133
|
+
workspace: str,
|
|
1134
|
+
runtime_id: str,
|
|
1135
|
+
) -> dict[str, t.Any]:
|
|
1136
|
+
"""GET /org/{org}/ws/{workspace}/runtimes/{runtime_id}/config - Get runtime config."""
|
|
1137
|
+
response = self.request(
|
|
1138
|
+
"GET",
|
|
1139
|
+
f"/org/{org}/ws/{workspace}/runtimes/{runtime_id}/config",
|
|
1140
|
+
)
|
|
1141
|
+
return t.cast("dict[str, t.Any]", response.json())
|
|
1142
|
+
|
|
1143
|
+
def update_runtime_config(
|
|
1144
|
+
self,
|
|
1145
|
+
org: str,
|
|
1146
|
+
workspace: str,
|
|
1147
|
+
runtime_id: str,
|
|
1148
|
+
config: dict[str, t.Any],
|
|
1149
|
+
) -> dict[str, t.Any]:
|
|
1150
|
+
"""PUT /org/{org}/ws/{workspace}/runtimes/{runtime_id}/config - Replace runtime config."""
|
|
1151
|
+
response = self.request(
|
|
1152
|
+
"PUT",
|
|
1153
|
+
f"/org/{org}/ws/{workspace}/runtimes/{runtime_id}/config",
|
|
1154
|
+
json_data={"config": config},
|
|
1155
|
+
)
|
|
1156
|
+
return t.cast("dict[str, t.Any]", response.json())
|
|
1157
|
+
|
|
1089
1158
|
def pause_runtime(self, org: str, workspace: str, runtime_id: str) -> dict[str, t.Any]:
|
|
1090
1159
|
"""POST /org/{org}/ws/{workspace}/runtimes/{runtime_id}/pause - Pause a runtime."""
|
|
1091
1160
|
response = self.request(
|
|
@@ -1722,6 +1791,23 @@ class ApiClient:
|
|
|
1722
1791
|
)
|
|
1723
1792
|
return t.cast("dict[str, t.Any]", response.json())
|
|
1724
1793
|
|
|
1794
|
+
def set_runtime_capability_flags(
|
|
1795
|
+
self,
|
|
1796
|
+
org: str,
|
|
1797
|
+
workspace: str,
|
|
1798
|
+
runtime_id: str,
|
|
1799
|
+
binding_id: str,
|
|
1800
|
+
*,
|
|
1801
|
+
flags: dict[str, bool | None],
|
|
1802
|
+
) -> dict[str, t.Any]:
|
|
1803
|
+
"""PATCH /org/{org}/ws/{workspace}/runtimes/{runtime_id}/capabilities/{binding_id}."""
|
|
1804
|
+
response = self.request(
|
|
1805
|
+
"PATCH",
|
|
1806
|
+
f"/org/{org}/ws/{workspace}/runtimes/{runtime_id}/capabilities/{binding_id}",
|
|
1807
|
+
json_data={"flags": flags},
|
|
1808
|
+
)
|
|
1809
|
+
return t.cast("dict[str, t.Any]", response.json())
|
|
1810
|
+
|
|
1725
1811
|
def update_runtime_capability(
|
|
1726
1812
|
self,
|
|
1727
1813
|
org: str,
|
|
@@ -1881,6 +1967,7 @@ class ApiClient:
|
|
|
1881
1967
|
title: str | None = None,
|
|
1882
1968
|
message_count: int = 0,
|
|
1883
1969
|
project_id: str | None = None,
|
|
1970
|
+
runtime_id: str | None = None,
|
|
1884
1971
|
) -> dict[str, t.Any]:
|
|
1885
1972
|
"""POST /org/{org}/ws/{workspace}/sessions - Create or save a session."""
|
|
1886
1973
|
payload: dict[str, t.Any] = {
|
|
@@ -1894,6 +1981,8 @@ class ApiClient:
|
|
|
1894
1981
|
payload["title"] = title
|
|
1895
1982
|
if project_id is not None:
|
|
1896
1983
|
payload["project_id"] = project_id
|
|
1984
|
+
if runtime_id is not None:
|
|
1985
|
+
payload["runtime_id"] = runtime_id
|
|
1897
1986
|
response = self.request("POST", f"/org/{org}/ws/{workspace}/sessions", json_data=payload)
|
|
1898
1987
|
return t.cast("dict[str, t.Any]", response.json())
|
|
1899
1988
|
|
|
@@ -2500,6 +2589,7 @@ class ApiClient:
|
|
|
2500
2589
|
*,
|
|
2501
2590
|
name: str,
|
|
2502
2591
|
project_id: str,
|
|
2592
|
+
runtime_id: str | None = None,
|
|
2503
2593
|
description: str | None = None,
|
|
2504
2594
|
session_id: str | None = None,
|
|
2505
2595
|
target_model: str | None = None,
|
|
@@ -2517,6 +2607,8 @@ class ApiClient:
|
|
|
2517
2607
|
payload["description"] = description
|
|
2518
2608
|
if session_id is not None:
|
|
2519
2609
|
payload["session_id"] = session_id
|
|
2610
|
+
if runtime_id is not None:
|
|
2611
|
+
payload["runtime_id"] = runtime_id
|
|
2520
2612
|
if target_model is not None:
|
|
2521
2613
|
payload["target_model"] = target_model
|
|
2522
2614
|
if attacker_model is not None:
|