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.
Files changed (495) hide show
  1. {dreadnode-2.0.10 → dreadnode-2.0.12}/PKG-INFO +2 -1
  2. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/agent.py +163 -6
  3. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/events.py +27 -0
  4. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/hooks.py +73 -79
  5. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/api/client.py +92 -0
  6. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/api/models.py +37 -2
  7. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/airt.py +14 -2
  8. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/args.py +24 -0
  9. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/capability.py +64 -19
  10. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/evaluation.py +24 -0
  11. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/main.py +11 -2
  12. dreadnode-2.0.12/dreadnode/app/cli/runtime.py +470 -0
  13. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/shared.py +1 -1
  14. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/task.py +132 -51
  15. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/verify.sh +1 -1
  16. dreadnode-2.0.12/dreadnode/app/client/__init__.py +1 -0
  17. dreadnode-2.0.12/dreadnode/app/client/interactive.py +541 -0
  18. dreadnode-2.0.12/dreadnode/app/client/managed_client.py +396 -0
  19. dreadnode-2.0.12/dreadnode/app/client/models.py +190 -0
  20. dreadnode-2.0.12/dreadnode/app/client/runtime_client.py +742 -0
  21. dreadnode-2.0.12/dreadnode/app/client/transports.py +436 -0
  22. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/config.py +54 -0
  23. dreadnode-2.0.12/dreadnode/app/env.py +66 -0
  24. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/main.py +37 -14
  25. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/print_mode.py +30 -25
  26. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/app.py +1096 -1393
  27. dreadnode-2.0.12/dreadnode/app/server/auth.py +66 -0
  28. dreadnode-2.0.12/dreadnode/app/server/capability_manager.py +772 -0
  29. dreadnode-2.0.12/dreadnode/app/server/model_resolution.py +125 -0
  30. dreadnode-2.0.12/dreadnode/app/server/prompt_registry.py +153 -0
  31. dreadnode-2.0.12/dreadnode/app/server/runtime_events.py +503 -0
  32. dreadnode-2.0.12/dreadnode/app/server/session_hydrator.py +216 -0
  33. dreadnode-2.0.12/dreadnode/app/server/session_persistence.py +351 -0
  34. dreadnode-2.0.12/dreadnode/app/server/session_policy.py +426 -0
  35. dreadnode-2.0.12/dreadnode/app/server/turn_coordinator.py +183 -0
  36. dreadnode-2.0.12/dreadnode/app/server/websocket.py +731 -0
  37. dreadnode-2.0.12/dreadnode/app/server/worker_manager.py +517 -0
  38. dreadnode-2.0.12/dreadnode/app/tui/app.py +3948 -0
  39. dreadnode-2.0.12/dreadnode/app/tui/auth_flow.py +195 -0
  40. dreadnode-2.0.12/dreadnode/app/tui/capabilities_manager.py +388 -0
  41. dreadnode-2.0.12/dreadnode/app/tui/command_dispatcher.py +754 -0
  42. dreadnode-2.0.12/dreadnode/app/tui/commands.py +79 -0
  43. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/connection.py +33 -30
  44. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/dreadnode.tcss +83 -333
  45. dreadnode-2.0.12/dreadnode/app/tui/error_handler.py +175 -0
  46. dreadnode-2.0.12/dreadnode/app/tui/model_manager.py +704 -0
  47. dreadnode-2.0.12/dreadnode/app/tui/profile_manager.py +1127 -0
  48. dreadnode-2.0.12/dreadnode/app/tui/screen_router.py +282 -0
  49. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/__init__.py +2 -2
  50. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/auth.py +11 -3
  51. dreadnode-2.0.12/dreadnode/app/tui/screens/base.py +257 -0
  52. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/capabilities.py +264 -68
  53. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/runtimes.py +136 -62
  54. dreadnode-2.0.12/dreadnode/app/tui/screens/services.py +700 -0
  55. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/sessions.py +122 -56
  56. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/workspaces.py +44 -47
  57. dreadnode-2.0.12/dreadnode/app/tui/sessions_manager.py +1625 -0
  58. dreadnode-2.0.12/dreadnode/app/tui/tool_format.py +311 -0
  59. dreadnode-2.0.12/dreadnode/app/tui/turn_coordinator.py +642 -0
  60. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/turn_lifecycle.py +25 -1
  61. dreadnode-2.0.12/dreadnode/app/tui/turn_reducer.py +348 -0
  62. dreadnode-2.0.12/dreadnode/app/tui/turn_state_phase.py +62 -0
  63. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/__init__.py +0 -2
  64. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/agent_suggester.py +1 -1
  65. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/composer.py +3 -1
  66. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/context_bar.py +14 -0
  67. dreadnode-2.0.12/dreadnode/app/tui/widgets/conversation.py +476 -0
  68. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/help_panel.py +2 -1
  69. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/tool.py +1 -1
  70. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/tools_dialog.py +7 -15
  71. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/welcome.py +1 -0
  72. dreadnode-2.0.12/dreadnode/app/tui/wire_events.py +464 -0
  73. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-runtime-reference/SKILL.md +3 -5
  74. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/__init__.py +2 -0
  75. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/capability.py +72 -1
  76. dreadnode-2.0.12/dreadnode/capabilities/flags.py +299 -0
  77. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/loader.py +438 -11
  78. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/sync.py +1 -1
  79. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/types.py +32 -1
  80. dreadnode-2.0.12/dreadnode/capabilities/worker.py +268 -0
  81. dreadnode-2.0.12/dreadnode/capabilities/worker_runner.py +588 -0
  82. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/exceptions.py +4 -0
  83. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/litellm_.py +66 -1
  84. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/jobs.py +1 -2
  85. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/manifest.py +3 -0
  86. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/oci.py +48 -0
  87. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/task_validation.py +97 -4
  88. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/storage.py +132 -6
  89. dreadnode-2.0.12/dreadnode/tracing/exporter.py +66 -0
  90. {dreadnode-2.0.10 → dreadnode-2.0.12}/pyproject.toml +2 -1
  91. dreadnode-2.0.10/dreadnode/app/cli/runtime.py +0 -55
  92. dreadnode-2.0.10/dreadnode/app/server/auth.py +0 -53
  93. dreadnode-2.0.10/dreadnode/app/tui/app.py +0 -5136
  94. dreadnode-2.0.10/dreadnode/app/tui/client.py +0 -1010
  95. dreadnode-2.0.10/dreadnode/app/tui/commands.py +0 -505
  96. dreadnode-2.0.10/dreadnode/app/tui/event_contract.py +0 -333
  97. dreadnode-2.0.10/dreadnode/app/tui/screens/base.py +0 -108
  98. dreadnode-2.0.10/dreadnode/app/tui/screens/mcp.py +0 -493
  99. dreadnode-2.0.10/dreadnode/app/tui/turn_reducer.py +0 -294
  100. dreadnode-2.0.10/dreadnode/app/tui/widgets/conversation.py +0 -332
  101. dreadnode-2.0.10/dreadnode/skills/__init__.py +0 -27
  102. dreadnode-2.0.10/dreadnode/tracing/exporter.py +0 -23
  103. {dreadnode-2.0.10 → dreadnode-2.0.12}/.gitignore +0 -0
  104. {dreadnode-2.0.10 → dreadnode-2.0.12}/LICENSE +0 -0
  105. {dreadnode-2.0.10 → dreadnode-2.0.12}/README.md +0 -0
  106. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/__init__.py +0 -0
  107. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/__main__.py +0 -0
  108. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/__init__.py +0 -0
  109. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/exceptions.py +0 -0
  110. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/format.py +0 -0
  111. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/__init__.py +0 -0
  112. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/auth.py +0 -0
  113. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/client.py +0 -0
  114. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/config.py +0 -0
  115. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/mcp/server.py +0 -0
  116. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/reactions.py +0 -0
  117. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/skills.py +0 -0
  118. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/stopping.py +0 -0
  119. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/subagent.py +0 -0
  120. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/tools.py +0 -0
  121. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/agents/trajectory.py +0 -0
  122. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/__init__.py +0 -0
  123. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/__init__.py +0 -0
  124. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/aggregator.py +0 -0
  125. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/classifier.py +0 -0
  126. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/compliance.py +0 -0
  127. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/engine.py +0 -0
  128. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/recommendations.py +0 -0
  129. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/analytics/types.py +0 -0
  130. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/assessment.py +0 -0
  131. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/autodan_turbo.py +0 -0
  132. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/beast.py +0 -0
  133. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/__init__.py +0 -0
  134. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/atlas.py +0 -0
  135. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/nist.py +0 -0
  136. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/owasp.py +0 -0
  137. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/owasp_agentic.py +0 -0
  138. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/compliance/saif.py +0 -0
  139. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/constants.py +0 -0
  140. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/crescendo.py +0 -0
  141. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/__init__.py +0 -0
  142. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/assets/audio/adversarial_query.mp3 +0 -0
  143. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/assets/image/bomb.jpg +0 -0
  144. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/assets/image/meth.png +0 -0
  145. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/prompts/adversarial_benchmark_subset.csv +0 -0
  146. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/prompts/ai_safety.csv +0 -0
  147. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/data_exfiltration.yaml +0 -0
  148. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/goal_hijacking.yaml +0 -0
  149. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/memory_poisoning.yaml +0 -0
  150. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/privilege_escalation.yaml +0 -0
  151. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/rce.yaml +0 -0
  152. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/scope_creep.yaml +0 -0
  153. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/tool_chaining.yaml +0 -0
  154. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/tool_selection_safety.yaml +0 -0
  155. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/unbounded_agency.yaml +0 -0
  156. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/rubrics/web_chatbot_security.yaml +0 -0
  157. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_1.yaml +0 -0
  158. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_2.yaml +0 -0
  159. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_3.yaml +0 -0
  160. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_4.yaml +0 -0
  161. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/data/templates/crescendo/variant_5.yaml +0 -0
  162. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/deep_inception.py +0 -0
  163. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/drattack.py +0 -0
  164. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/events.py +0 -0
  165. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/goat.py +0 -0
  166. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/gptfuzzer.py +0 -0
  167. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/image.py +0 -0
  168. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/multimodal.py +0 -0
  169. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/pair.py +0 -0
  170. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/prompt.py +0 -0
  171. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/rainbow.py +0 -0
  172. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/renellm.py +0 -0
  173. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/__init__.py +0 -0
  174. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/json_report.py +0 -0
  175. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/llm_summary.py +0 -0
  176. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/reporting/markdown.py +0 -0
  177. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/airt/tap.py +0 -0
  178. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/__init__.py +0 -0
  179. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/api/__init__.py +0 -0
  180. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/__init__.py +0 -0
  181. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/dataset.py +0 -0
  182. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/model.py +0 -0
  183. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/optimize.py +0 -0
  184. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/sandbox.py +0 -0
  185. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/__init__.py +0 -0
  186. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/__init__.py +0 -0
  187. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/challenge/Dockerfile +0 -0
  188. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/docker-compose.yaml +0 -0
  189. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/provision.sh +0 -0
  190. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/solution.sh +0 -0
  191. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/task-remote.yaml.tmpl +0 -0
  192. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/task.yaml.tmpl +0 -0
  193. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/templates/init/teardown.sh +0 -0
  194. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/train.py +0 -0
  195. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/cli/worlds.py +0 -0
  196. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/model_catalog.py +0 -0
  197. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/__init__.py +0 -0
  198. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/prompt.py +0 -0
  199. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/server/utils.py +0 -0
  200. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/__init__.py +0 -0
  201. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/model_variants.py +0 -0
  202. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/runtime_cache.py +0 -0
  203. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/capability_docs.py +0 -0
  204. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/connection_error.py +0 -0
  205. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/console.py +0 -0
  206. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/environments.py +0 -0
  207. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/evaluations.py +0 -0
  208. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/model_picker.py +0 -0
  209. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/models.py +0 -0
  210. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/raw_spans.py +0 -0
  211. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/sandboxes.py +0 -0
  212. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/secrets.py +0 -0
  213. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/theme_showcase.py +0 -0
  214. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/screens/traces.py +0 -0
  215. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/spans_reader.py +0 -0
  216. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/theme.py +0 -0
  217. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/update_check.py +0 -0
  218. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/agent_dialog.py +0 -0
  219. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/flash.py +0 -0
  220. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/header_bar.py +0 -0
  221. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/mention_overlay.py +0 -0
  222. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/message_queue.py +0 -0
  223. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/overlay_mixin.py +0 -0
  224. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/permission_prompt.py +0 -0
  225. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/profile_dialog.py +0 -0
  226. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/prompt_info.py +0 -0
  227. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/session_sidebar.py +0 -0
  228. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/skills_dialog.py +0 -0
  229. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/slash_overlay.py +0 -0
  230. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/status_bar.py +0 -0
  231. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/throbber.py +0 -0
  232. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/tool_progress.py +0 -0
  233. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/app/tui/widgets/whoami.py +0 -0
  234. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/__init__.py +0 -0
  235. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/agents/dreadnode.md +0 -0
  236. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/capability.yaml +0 -0
  237. {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/SKILL.md +0 -0
  238. {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/capability-components.md +0 -0
  239. {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/capability-improvement.md +0 -0
  240. {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/creating-capabilities/runtime-default-capability.md +0 -0
  241. {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/dreadnode-cli/SKILL.md +0 -0
  242. {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/dreadnode-cli/references/command-groups.md +0 -0
  243. {dreadnode-2.0.10 → dreadnode-2.0.12/dreadnode/builtin_capabilities}/dreadnode/skills/dreadnode-cli/references/tui-crosswalk.md +0 -0
  244. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/builtin_capabilities/dreadnode/system-prompt.md +0 -0
  245. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/capabilities/tool_rules.py +0 -0
  246. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/__init__.py +0 -0
  247. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/conditions.py +0 -0
  248. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/discovery.py +0 -0
  249. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/environment.py +0 -0
  250. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/execution.py +0 -0
  251. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/hook.py +0 -0
  252. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/judge.py +0 -0
  253. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/load.py +0 -0
  254. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/log.py +0 -0
  255. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/__init__.py +0 -0
  256. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/config.py +0 -0
  257. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/context.py +0 -0
  258. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/hydrate.py +0 -0
  259. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/meta/introspect.py +0 -0
  260. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/metric.py +0 -0
  261. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/object.py +0 -0
  262. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/scorer.py +0 -0
  263. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/serialization.py +0 -0
  264. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/stopping.py +0 -0
  265. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/task.py +0 -0
  266. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/transforms.py +0 -0
  267. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/__init__.py +0 -0
  268. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/audio.py +0 -0
  269. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/base.py +0 -0
  270. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/common.py +0 -0
  271. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/image.py +0 -0
  272. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/object_3d.py +0 -0
  273. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/table.py +0 -0
  274. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/text.py +0 -0
  275. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/types/video.py +0 -0
  276. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/core/util.py +0 -0
  277. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/__init__.py +0 -0
  278. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/dataset.py +0 -0
  279. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/hf.py +0 -0
  280. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/datasets/local.py +0 -0
  281. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/__init__.py +0 -0
  282. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/console.py +0 -0
  283. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/evaluation.py +0 -0
  284. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/events.py +0 -0
  285. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/format.py +0 -0
  286. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/result.py +0 -0
  287. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/evaluations/sample.py +0 -0
  288. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/__init__.py +0 -0
  289. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/caching.py +0 -0
  290. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/chat.py +0 -0
  291. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/data.py +0 -0
  292. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/exceptions.py +0 -0
  293. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/__init__.py +0 -0
  294. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/base.py +0 -0
  295. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/http.py +0 -0
  296. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/transformers_.py +0 -0
  297. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/generator/vllm_.py +0 -0
  298. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/message.py +0 -0
  299. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/models.py +0 -0
  300. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/parsing.py +0 -0
  301. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/tokenizer/__init__.py +0 -0
  302. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/tokenizer/base.py +0 -0
  303. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/tokenizer/transformers_.py +0 -0
  304. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/generators/utils.py +0 -0
  305. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/__init__.py +0 -0
  306. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/hf.py +0 -0
  307. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/local.py +0 -0
  308. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/models/model.py +0 -0
  309. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/__init__.py +0 -0
  310. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/adapters/__init__.py +0 -0
  311. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/adapters/agent.py +0 -0
  312. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/adapters/stack.py +0 -0
  313. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/api.py +0 -0
  314. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/backends/__init__.py +0 -0
  315. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/backends/base.py +0 -0
  316. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/backends/gepa.py +0 -0
  317. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/collectors.py +0 -0
  318. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/config.py +0 -0
  319. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/console.py +0 -0
  320. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/events.py +0 -0
  321. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/format.py +0 -0
  322. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/result.py +0 -0
  323. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/sampler.py +0 -0
  324. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/sampling.py +0 -0
  325. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/search.py +0 -0
  326. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/stopping.py +0 -0
  327. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/study.py +0 -0
  328. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/optimization/trial.py +0 -0
  329. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/__init__.py +0 -0
  330. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/loader.py +0 -0
  331. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/packaging/package.py +0 -0
  332. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/py.typed +0 -0
  333. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/__init__.py +0 -0
  334. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/boundary.py +0 -0
  335. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/fuzzing.py +0 -0
  336. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/graph.py +0 -0
  337. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/grid.py +0 -0
  338. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/image.py +0 -0
  339. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/mapelites.py +0 -0
  340. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/optuna.py +0 -0
  341. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/random.py +0 -0
  342. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/registry.py +0 -0
  343. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/samplers/strategy.py +0 -0
  344. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/__init__.py +0 -0
  345. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/advanced_jailbreak_detection.py +0 -0
  346. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/agent_security.py +0 -0
  347. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/agentic.py +0 -0
  348. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/agentic_workflow.py +0 -0
  349. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/classification.py +0 -0
  350. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/consistency.py +0 -0
  351. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/contains.py +0 -0
  352. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/cosine_sim.py +0 -0
  353. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/credentials.py +0 -0
  354. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/crucible.py +0 -0
  355. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/documentation_security.py +0 -0
  356. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/exfiltration_detection.py +0 -0
  357. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/format.py +0 -0
  358. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/harm.py +0 -0
  359. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/ide_security.py +0 -0
  360. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/image.py +0 -0
  361. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/json.py +0 -0
  362. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/judge.py +0 -0
  363. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/length.py +0 -0
  364. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/lexical.py +0 -0
  365. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/mcp_security.py +0 -0
  366. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/memorization.py +0 -0
  367. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/multi_agent_security.py +0 -0
  368. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/pii.py +0 -0
  369. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/prompt_leak.py +0 -0
  370. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/readability.py +0 -0
  371. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/reasoning_security.py +0 -0
  372. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/sentiment.py +0 -0
  373. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/scorers/similarity.py +0 -0
  374. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/__init__.py +0 -0
  375. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/providers.py +0 -0
  376. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/storage/session_store.py +0 -0
  377. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/__init__.py +0 -0
  378. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/_ripgrep.py +0 -0
  379. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/apply_patch.py +0 -0
  380. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/dreadnode_cli.py +0 -0
  381. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/editing.py +0 -0
  382. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/execute.py +0 -0
  383. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/fetch.py +0 -0
  384. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/glob.py +0 -0
  385. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/grep.py +0 -0
  386. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/interaction.py +0 -0
  387. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/ls.py +0 -0
  388. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/memory.py +0 -0
  389. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/read.py +0 -0
  390. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/report.py +0 -0
  391. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/task.py +0 -0
  392. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/think.py +0 -0
  393. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/todo.py +0 -0
  394. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/trajectory_search.py +0 -0
  395. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/web_search.py +0 -0
  396. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tools/write.py +0 -0
  397. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/__init__.py +0 -0
  398. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/constants.py +0 -0
  399. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/convert.py +0 -0
  400. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/exporters.py +0 -0
  401. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/span.py +0 -0
  402. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/spans.py +0 -0
  403. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/tracing/trace_converter.py +0 -0
  404. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/__init__.py +0 -0
  405. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/base.py +0 -0
  406. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/dpo.py +0 -0
  407. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/__init__.py +0 -0
  408. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/_common.py +0 -0
  409. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/rl.py +0 -0
  410. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/sft.py +0 -0
  411. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/etl/worlds.py +0 -0
  412. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/events.py +0 -0
  413. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/grpo.py +0 -0
  414. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/jobs.py +0 -0
  415. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ppo.py +0 -0
  416. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/prime.py +0 -0
  417. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/__init__.py +0 -0
  418. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/async_trainer.py +0 -0
  419. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/config.py +0 -0
  420. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/coordinator.py +0 -0
  421. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/distributed.py +0 -0
  422. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/dpo.py +0 -0
  423. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/experience.py +0 -0
  424. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/fsdp2_learner.py +0 -0
  425. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/inference.py +0 -0
  426. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/learner.py +0 -0
  427. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/multi_turn.py +0 -0
  428. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/ppo.py +0 -0
  429. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/reward_model.py +0 -0
  430. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/rollout_env.py +0 -0
  431. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/rollout_worker.py +0 -0
  432. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/sft.py +0 -0
  433. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/ray/trainer.py +0 -0
  434. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/recipes.py +0 -0
  435. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/__init__.py +0 -0
  436. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/aggregator.py +0 -0
  437. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/functions.py +0 -0
  438. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/scorer_bridge.py +0 -0
  439. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/shaping.py +0 -0
  440. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rewards/types.py +0 -0
  441. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/__init__.py +0 -0
  442. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/adapters.py +0 -0
  443. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/orchestrator.py +0 -0
  444. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/types.py +0 -0
  445. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/rollouts/worlds.py +0 -0
  446. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/serving/__init__.py +0 -0
  447. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/serving/vllm_client.py +0 -0
  448. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/sft.py +0 -0
  449. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/__init__.py +0 -0
  450. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/config.py +0 -0
  451. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/data.py +0 -0
  452. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/renderer.py +0 -0
  453. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/rl.py +0 -0
  454. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker/trainer.py +0 -0
  455. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/tinker_sft.py +0 -0
  456. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/training/utils.py +0 -0
  457. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/__init__.py +0 -0
  458. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/advanced_jailbreak.py +0 -0
  459. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/adversarial_suffix.py +0 -0
  460. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/agent_skill.py +0 -0
  461. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/agentic_workflow.py +0 -0
  462. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/audio.py +0 -0
  463. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/browser_agent_attacks.py +0 -0
  464. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/cipher.py +0 -0
  465. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/constitutional.py +0 -0
  466. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/document.py +0 -0
  467. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/documentation_poison.py +0 -0
  468. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/encoding.py +0 -0
  469. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/exfiltration.py +0 -0
  470. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/flip_attack.py +0 -0
  471. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/guardrail_bypass.py +0 -0
  472. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/ide_injection.py +0 -0
  473. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/image.py +0 -0
  474. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/injection.py +0 -0
  475. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/json_tools.py +0 -0
  476. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/language.py +0 -0
  477. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/logic_bomb.py +0 -0
  478. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/mcp_attacks.py +0 -0
  479. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/multi_agent_attacks.py +0 -0
  480. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/persuasion.py +0 -0
  481. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/perturbation.py +0 -0
  482. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/pii_extraction.py +0 -0
  483. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/pythonic_tools.py +0 -0
  484. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/rag_poisoning.py +0 -0
  485. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/reasoning_attacks.py +0 -0
  486. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/refine.py +0 -0
  487. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/response_steering.py +0 -0
  488. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/stylistic.py +0 -0
  489. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/substitution.py +0 -0
  490. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/swap.py +0 -0
  491. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/system_prompt_extraction.py +0 -0
  492. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/text.py +0 -0
  493. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/video.py +0 -0
  494. {dreadnode-2.0.10 → dreadnode-2.0.12}/dreadnode/transforms/xml_tools.py +0 -0
  495. {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.10
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
- boundary = find_summarization_boundary(work, min_messages_to_keep=10)
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
- f"Overflow recovery: summarizing {len(to_summarize)} messages, keeping {len(to_keep)}"
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
- """Core agent hooks: backoff, metrics, and summarization.
1
+ """Optional agent hooks: tool metrics and conversation summarization.
2
2
 
3
- These hooks are included by default in every agent created by the server.
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 finds the latest safe split point that
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
- best_boundary = 0
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
- best_boundary = i + 1
292
- return best_boundary
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: