dreadnode 2.0.44__py3-none-any.whl → 2.0.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dreadnode/airt/_base.py CHANGED
@@ -89,12 +89,27 @@ class BlackBoxAttack:
89
89
  self.rng = np.random.default_rng(seed)
90
90
  self.airt_assessment_id = airt_assessment_id
91
91
  self.airt_target_model = airt_target_model or target.name
92
- self.airt_goal = airt_goal or self.default_goal
92
+ # Make the default goal target-aware so the same objective against
93
+ # different models counts as distinct goals on the dashboard (e.g. evasion
94
+ # of the fraud, digits, and sentiment classifiers = 3 goals, not 1). An
95
+ # explicit airt_goal is respected verbatim; only the default is suffixed.
96
+ self.airt_goal = airt_goal or (
97
+ f"{self.default_goal} on {self.airt_target_model}"
98
+ if self.airt_target_model
99
+ else self.default_goal
100
+ )
93
101
  self.airt_goal_category = airt_goal_category or self.default_goal_category
94
102
  self._query_count = 0
95
103
  #: A sample of real (input -> prediction) pairs so the finding can show
96
104
  #: the actual queries the attacker sent, not just the total count.
97
105
  self._query_samples: list[dict[str, t.Any]] = []
106
+ #: Count of trial spans emitted this run. Each algorithm iteration is one
107
+ #: trial (mirroring how a generative Study counts a trial per step), so the
108
+ #: platform can count trials / incurred budget for traditional-ML attacks.
109
+ self._trials_emitted = 0
110
+ #: Hard safety cap on trial spans so a pathologically long loop cannot
111
+ #: explode the trace tree.
112
+ self._max_trial_spans = 1000
98
113
 
99
114
  # -- target I/O -------------------------------------------------------- #
100
115
  async def _query(self, xs: t.Sequence[QueryInput]) -> list[Prediction]:
@@ -146,28 +161,74 @@ class BlackBoxAttack:
146
161
  metrics: dict[str, float] | None = None,
147
162
  **attrs: t.Any,
148
163
  ) -> None:
149
- """Emit a point-in-time span for one algorithm iteration, recording the
164
+ """Emit a **trial** span for one algorithm iteration, recording the
150
165
  intermediate adversarial state so the full attack trajectory is
151
- inspectable in the Traces tab.
166
+ inspectable in the Traces tab and every iteration is counted as a trial.
167
+
168
+ One iteration == one trial: this mirrors how a generative ``Study`` emits
169
+ a trial span per refinement step, so the platform counts trials (and the
170
+ incurred query budget) for traditional-ML attacks the same way. The span
171
+ carries the AIRT assessment/attack attributes and is a child of the run's
172
+ ``study`` span (opened in :meth:`run`), which is how ClickHouse links a
173
+ trial to its assessment.
152
174
 
153
175
  ``input`` is the current candidate (perturbed text / feature vector /
154
176
  image preview) the attacker sent; ``output`` is the target's response at
155
- this step (predicted label + confidence); ``metrics`` are the scalar
156
- signals (distance, confidence, fidelity) plotted over the run. Capped
157
- (first 20 iterations, then every 10th) so a long run does not explode the
177
+ this step; ``metrics`` are the scalar signals (distance, confidence,
178
+ fidelity) plotted over the run. The lightweight trial span is emitted
179
+ every iteration; the heavier input/output previews are attached only for
180
+ the first 20 iterations then every 10th, so a long run does not bloat the
158
181
  trace tree."""
159
- if iteration >= 20 and iteration % 10 != 0:
182
+ if self._trials_emitted >= self._max_trial_spans:
160
183
  return
161
- with self._phase(f"step {iteration}", iteration=iteration, **attrs) as span:
162
- if span is None or not hasattr(span, "log_input"):
184
+ self._trials_emitted += 1
185
+ heavy = iteration < 20 or iteration % 10 == 0
186
+ with self._trial(iteration, **attrs) as span:
187
+ if span is None or not hasattr(span, "log_metric"):
163
188
  return
164
189
  with suppress(Exception):
165
- if input is not None:
166
- span.log_input("candidate", input)
167
- if output is not None:
168
- span.log_output("prediction", output)
169
190
  for key, value in (metrics or {}).items():
170
191
  span.log_metric(key, float(value), step=iteration)
192
+ if heavy and input is not None and hasattr(span, "log_input"):
193
+ span.log_input("candidate", input)
194
+ if heavy and output is not None and hasattr(span, "log_output"):
195
+ span.log_output("prediction", output)
196
+
197
+ def _trial(self, iteration: int, **attrs: t.Any) -> t.Any:
198
+ """Open a ``trial`` span for one iteration, tagged with the AIRT
199
+ assessment/attack attributes so it is counted against this assessment."""
200
+ try:
201
+ from dreadnode.tracing.spans import trial_span
202
+
203
+ ctx = trial_span(
204
+ trial_id=f"{self._attack_name}-{iteration}-{self._trials_emitted}",
205
+ step=int(iteration),
206
+ airt_assessment_id=self.airt_assessment_id,
207
+ airt_trial_index=int(iteration),
208
+ airt_attack_name=self._attack_name,
209
+ airt_attack_domain=self.attack_domain,
210
+ airt_target_model=self.airt_target_model,
211
+ airt_goal=self.airt_goal,
212
+ airt_goal_category=self.airt_goal_category,
213
+ airt_input_modality=self.modality,
214
+ )
215
+ except Exception:
216
+ return nullcontext()
217
+ if not attrs:
218
+ return ctx
219
+ # Fold any per-iteration attrs (e.g. distance norm) onto the span.
220
+ from contextlib import contextmanager
221
+
222
+ @contextmanager
223
+ def _wrapped() -> t.Iterator[t.Any]:
224
+ with ctx as span:
225
+ if span is not None and hasattr(span, "set_attribute"):
226
+ with suppress(Exception):
227
+ for key, value in attrs.items():
228
+ span.set_attribute(str(key), value)
229
+ yield span
230
+
231
+ return _wrapped()
171
232
 
172
233
  # -- run scaffold ------------------------------------------------------ #
173
234
  @property
@@ -792,7 +792,9 @@ def create(
792
792
  max_steps: Maximum agent tool/generation steps per sample.
793
793
  Enforced via a headless session policy; the task timeout still
794
794
  applies as a wall-clock bound.
795
- cleanup_policy: Sandbox cleanup policy.
795
+ cleanup_policy: Sandbox cleanup policy. Hosted E2B evaluations support
796
+ only ``always``; ``on_success`` is available on Docker and
797
+ OpenSandbox deployments.
796
798
  judge_model: Override the judge model for all tasks in this evaluation.
797
799
  egress: Additional outbound destination for this run's sandboxes, as an
798
800
  FQDN, a '*.suffix' wildcard, an IP, or a CIDR (repeatable). Unions
dreadnode/app/cli/main.py CHANGED
@@ -1,9 +1,11 @@
1
1
  """Root cyclopts App with top-level commands (tui, login, serve)."""
2
2
 
3
+ import importlib
3
4
  import importlib.metadata
4
5
  import os
5
6
  import secrets
6
7
  import sys
8
+ import threading
7
9
  import time
8
10
  import typing as t
9
11
 
@@ -14,25 +16,8 @@ import cyclopts
14
16
  from loguru import logger
15
17
  from rich.panel import Panel
16
18
 
17
- from dreadnode.app.cli.airt import cli as airt_cli
18
19
  from dreadnode.app.cli.args import RESUME_PICK_SENTINEL, PlatformArgs, TuiArgs
19
- from dreadnode.app.cli.capability import cli as capability_cli
20
- from dreadnode.app.cli.dataset import cli as dataset_cli
21
- from dreadnode.app.cli.environment import cli as environment_cli
22
- from dreadnode.app.cli.evaluation import cli as evaluation_cli
23
- from dreadnode.app.cli.inference_model import cli as inference_model_cli
24
- from dreadnode.app.cli.judge import cli as judge_cli
25
- from dreadnode.app.cli.model import cli as model_cli
26
- from dreadnode.app.cli.optimize import cli as optimize_cli
27
- from dreadnode.app.cli.runtime import cli as runtime_cli
28
- from dreadnode.app.cli.sandbox import cli as sandbox_cli
29
- from dreadnode.app.cli.secret import cli as secret_cli
30
- from dreadnode.app.cli.session import cli as session_cli
31
20
  from dreadnode.app.cli.shared import console, print_error, print_success
32
- from dreadnode.app.cli.task import cli as task_cli
33
- from dreadnode.app.cli.task_set import cli as task_set_cli
34
- from dreadnode.app.cli.train import cli as train_cli
35
- from dreadnode.app.cli.worlds import cli as worlds_cli
36
21
  from dreadnode.app.model_catalog import resolve_model
37
22
  from dreadnode.core.tls import format_tls_error
38
23
 
@@ -68,7 +53,55 @@ def _render_profile_context(profile: t.Any) -> str:
68
53
  # Root app
69
54
  # ---------------------------------------------------------------------------
70
55
 
71
- cli = cyclopts.App(
56
+
57
+ class _LazyCommandApp(cyclopts.App):
58
+ """Root app that resolves subcommands from the tokens it is handed.
59
+
60
+ Subcommands import on demand (see ``_LAZY_COMMANDS`` below), so anything
61
+ reaching this object directly rather than through ``run()`` would otherwise
62
+ see only the commands defined in this module. That failure is silent:
63
+ cyclopts answers an unregistered command by printing the *root* help and
64
+ exiting 0, so the caller gets a plausible wrong answer instead of an error.
65
+
66
+ Preparing inside the entry points covers every caller rather than the two
67
+ that happen to be known today — the agent's ``dreadnode_cli`` tool calls
68
+ ``cli(tokens)``, tests call ``parse_args``, and the next one has not been
69
+ written yet. Registration is idempotent, so the repeat calls cyclopts makes
70
+ internally cost a set lookup.
71
+ """
72
+
73
+ @staticmethod
74
+ def _as_tokens(tokens: "None | str | t.Iterable[str]") -> list[str]:
75
+ if tokens is None:
76
+ return sys.argv[1:]
77
+ if isinstance(tokens, str):
78
+ return tokens.split()
79
+ return list(tokens)
80
+
81
+ def __call__(self, tokens: "None | str | t.Iterable[str]" = None, **kwargs: t.Any) -> t.Any:
82
+ _prepare_commands(self._as_tokens(tokens))
83
+ return super().__call__(tokens, **kwargs)
84
+
85
+ def parse_args( # type: ignore[override]
86
+ self, tokens: "None | str | t.Iterable[str]" = None, **kwargs: t.Any
87
+ ) -> t.Any:
88
+ _prepare_commands(self._as_tokens(tokens))
89
+ return super().parse_args(tokens, **kwargs)
90
+
91
+ def parse_known_args( # type: ignore[override]
92
+ self, tokens: "None | str | t.Iterable[str]" = None, **kwargs: t.Any
93
+ ) -> t.Any:
94
+ _prepare_commands(self._as_tokens(tokens))
95
+ return super().parse_known_args(tokens, **kwargs)
96
+
97
+ def parse_commands( # type: ignore[override]
98
+ self, tokens: "None | str | t.Iterable[str]" = None, **kwargs: t.Any
99
+ ) -> t.Any:
100
+ _prepare_commands(self._as_tokens(tokens))
101
+ return super().parse_commands(tokens, **kwargs)
102
+
103
+
104
+ cli = _LazyCommandApp(
72
105
  name="dreadnode",
73
106
  help="Dreadnode CLI — agent development and platform tooling.",
74
107
  version=_get_version_safe(),
@@ -85,23 +118,99 @@ cli["--install-completion"].group = "Meta"
85
118
  # Subcommands
86
119
  # ---------------------------------------------------------------------------
87
120
 
88
- cli.command(airt_cli)
89
- cli.command(capability_cli)
90
- cli.command(dataset_cli)
91
- cli.command(environment_cli, alias="env")
92
- cli.command(evaluation_cli)
93
- cli.command(inference_model_cli, alias="llm")
94
- cli.command(judge_cli)
95
- cli.command(model_cli)
96
- cli.command(optimize_cli)
97
- cli.command(runtime_cli)
98
- cli.command(sandbox_cli)
99
- cli.command(secret_cli)
100
- cli.command(session_cli)
101
- cli.command(task_cli)
102
- cli.command(task_set_cli)
103
- cli.command(train_cli)
104
- cli.command(worlds_cli)
121
+ # Subcommands are imported when one is invoked, not when the CLI loads.
122
+ #
123
+ # Registering them eagerly meant every invocation paid for every command:
124
+ # `dreadnode serve` imported the AIRT tree and, under it, pandas, pyarrow,
125
+ # numpy and optuna, none of which a running runtime uses. That was the
126
+ # majority of a runtime's cold start (ENG-8259).
127
+ #
128
+ # This is the same lazy-table shape the SDK already uses for its own
129
+ # subpackages (`__lazy_submodules__` and friends in `dreadnode/__init__.py`);
130
+ # cyclopts has no native support for deferred subcommands, so registration is
131
+ # driven from the invoked token instead.
132
+ #
133
+ # Keys are the command name as it is typed. The value is the module holding
134
+ # the sub-app and its alias, if it has one.
135
+ _LAZY_COMMANDS: dict[str, tuple[str, str | None]] = {
136
+ "airt": ("dreadnode.app.cli.airt", None),
137
+ "capability": ("dreadnode.app.cli.capability", None),
138
+ "dataset": ("dreadnode.app.cli.dataset", None),
139
+ "environment": ("dreadnode.app.cli.environment", "env"),
140
+ "evaluation": ("dreadnode.app.cli.evaluation", None),
141
+ "inference-model": ("dreadnode.app.cli.inference_model", "llm"),
142
+ "judge": ("dreadnode.app.cli.judge", None),
143
+ "model": ("dreadnode.app.cli.model", None),
144
+ "optimize": ("dreadnode.app.cli.optimize", None),
145
+ "runtime": ("dreadnode.app.cli.runtime", None),
146
+ "sandbox": ("dreadnode.app.cli.sandbox", None),
147
+ "secret": ("dreadnode.app.cli.secret", None),
148
+ "session": ("dreadnode.app.cli.session", None),
149
+ "task": ("dreadnode.app.cli.task", None),
150
+ "task-set": ("dreadnode.app.cli.task_set", None),
151
+ "train": ("dreadnode.app.cli.train", None),
152
+ "worlds": ("dreadnode.app.cli.worlds", None),
153
+ }
154
+
155
+ _COMMAND_ALIASES: dict[str, str] = {
156
+ alias: name for name, (_, alias) in _LAZY_COMMANDS.items() if alias
157
+ }
158
+
159
+ _registered_commands: set[str] = set()
160
+ # `dreadnode serve` hosts concurrent sessions and the agent's CLI tool is
161
+ # dispatched with asyncio.to_thread, so two threads can reach the same
162
+ # unregistered command at once. cyclopts raises CommandCollisionError on a
163
+ # duplicate name, which would fail the second caller's invocation.
164
+ _registration_lock = threading.Lock()
165
+
166
+
167
+ def register_command(name: str) -> bool:
168
+ """Register one lazy subcommand, importing its module. True if it exists.
169
+
170
+ Idempotent, so a caller may register a command that is already present.
171
+ """
172
+ primary = _COMMAND_ALIASES.get(name, name)
173
+ entry = _LAZY_COMMANDS.get(primary)
174
+ if entry is None:
175
+ return False
176
+ if primary in _registered_commands:
177
+ return True
178
+ with _registration_lock:
179
+ # Re-check under the lock: another thread may have registered it while
180
+ # this one waited, and registering twice raises.
181
+ if primary not in _registered_commands:
182
+ module_path, alias = entry
183
+ subapp = importlib.import_module(module_path).cli
184
+ if alias:
185
+ cli.command(subapp, alias=alias)
186
+ else:
187
+ cli.command(subapp)
188
+ _registered_commands.add(primary)
189
+ return True
190
+
191
+
192
+ def register_all_commands() -> None:
193
+ """Register every subcommand, importing all of them.
194
+
195
+ Needed whenever the whole command tree has to be visible at once: help,
196
+ shell completion, an unrecognised command that should be reported against
197
+ the full list, and tests that assert on the CLI surface.
198
+ """
199
+ for name in _LAZY_COMMANDS:
200
+ register_command(name)
201
+
202
+
203
+ def _prepare_commands(tokens: t.Sequence[str]) -> None:
204
+ """Import whatever the invocation needs, and nothing else."""
205
+ for token in tokens:
206
+ if token.startswith("-"):
207
+ continue # a flag before the command, e.g. `dreadnode --version`
208
+ if register_command(token):
209
+ return
210
+ if token in cli:
211
+ return # defined here rather than in a subcommand module
212
+ break # unknown: fall through so the error lists every command
213
+ register_all_commands()
105
214
 
106
215
 
107
216
  # ---------------------------------------------------------------------------
@@ -115,6 +224,7 @@ def _meta(
115
224
  ) -> None:
116
225
  try:
117
226
  console.print()
227
+ _prepare_commands(tokens)
118
228
  cli(tokens)
119
229
  except Exception as exc:
120
230
  if DEBUG:
@@ -645,6 +755,11 @@ def run(argv: list[str] | None = None) -> None:
645
755
  try:
646
756
  tokens = argv if argv is not None else sys.argv[1:]
647
757
  tokens = _rewrite_bare_resume(list(tokens))
758
+ # Before dispatch, not inside the meta handler: cyclopts answers
759
+ # `--help` and `--version` on the meta app itself and never runs the
760
+ # handler body, so registering there would leave the help listing
761
+ # showing only the commands defined in this module.
762
+ _prepare_commands(tokens)
648
763
  cli.meta(tokens)
649
764
  except SystemExit as exc:
650
765
  if exc.code not in (None, 0):
@@ -611,6 +611,31 @@ def create_agent(
611
611
  # =============================================================================
612
612
 
613
613
 
614
+ def _configure_litellm_env() -> None:
615
+ """Configure litellm through the environment, before anything imports it.
616
+
617
+ litellm reads these at import time:
618
+
619
+ drop_params = bool(os.getenv("LITELLM_DROP_PARAMS", False))
620
+ modify_params = bool(os.getenv("LITELLM_MODIFY_PARAMS", False))
621
+
622
+ so setting them configures whoever imports litellm first — the warm thread,
623
+ or a request that beats it. Going through the environment is what lets the
624
+ import itself come off the readiness path: this needs no import of its own.
625
+
626
+ ``suppress_debug_info`` has no environment hook and is set as an attribute
627
+ by the warm thread. It only silences debug prints, so the warm window costs
628
+ noise rather than correctness.
629
+
630
+ ``LITELLM_LOCAL_MODEL_COST_MAP`` stops litellm fetching its model-cost map
631
+ over the network on first import. The platform sets it in the sandbox
632
+ environment; this covers a local ``dreadnode serve``.
633
+ """
634
+ os.environ.setdefault("LITELLM_DROP_PARAMS", "True")
635
+ os.environ.setdefault("LITELLM_MODIFY_PARAMS", "True")
636
+ os.environ.setdefault("LITELLM_LOCAL_MODEL_COST_MAP", "True")
637
+
638
+
614
639
  def _warm_litellm() -> None:
615
640
  """Import litellm so the first chat turn doesn't pay the cold-import cost.
616
641
 
@@ -622,10 +647,13 @@ def _warm_litellm() -> None:
622
647
  """
623
648
  _t0 = time.perf_counter()
624
649
  try:
625
- # Skip the httpx.get() to GitHub for model pricing on import.
626
- os.environ.setdefault("LITELLM_LOCAL_MODEL_COST_MAP", "True")
627
- import litellm
628
- import litellm.exceptions
650
+ # Belt and braces: the lifecycle sets these, but this runs on other
651
+ # paths too and must never be the one that imports litellm unconfigured.
652
+ _configure_litellm_env()
653
+ with startup_clock.timed("litellm.import"):
654
+ import litellm
655
+ import litellm.exceptions
656
+ startup_clock.mark("litellm_imported")
629
657
 
630
658
  litellm.drop_params = True
631
659
  litellm.suppress_debug_info = True # ty: ignore[invalid-assignment]
@@ -695,23 +723,18 @@ async def server_lifecycle() -> t.AsyncIterator[None]:
695
723
  await registry.worker_manager.start(registry)
696
724
  state.startup.mark("workers_started")
697
725
 
698
- # Set critical litellm flags eagerly (before background import) so they
699
- # are active even if a chat request arrives before warmup completes.
700
- # This import is the whole litellm cost on the readiness path; the warm
701
- # thread below finds it already done. Timed so that shows up as its own
702
- # number rather than hiding inside "connecting".
703
- try:
704
- with startup_clock.timed("litellm.import"):
705
- import litellm
706
- state.startup.mark("litellm_imported")
707
-
708
- litellm.drop_params = True
709
- litellm.suppress_debug_info = True # ty: ignore[invalid-assignment]
710
- except Exception: # noqa: S110 - _warm_litellm will retry the full import
711
- pass
712
-
713
- # Warm litellm in a background thread so the health endpoint goes live
714
- # immediately while the ~2s import finishes in the background.
726
+ _configure_litellm_env()
727
+
728
+ # litellm is deliberately NOT imported here. It was the largest single item
729
+ # on the readiness path (~1.8s on a laptop, ~4.3s on a sandbox) and it ran
730
+ # last, so readiness waited on it (ENG-8259). The warm thread below still
731
+ # shifts the import cost off the first chat turn; it just no longer holds
732
+ # readiness open while it does.
733
+ #
734
+ # The flags this block used to import litellm to set are covered by the
735
+ # environment above. Note that LiteLLMGenerator.__post_model_init__ looks
736
+ # like it sets them too, but that is not a pydantic hook and never runs —
737
+ # see ENG-8259. Do not rely on it.
715
738
  warm_future = asyncio.get_running_loop().run_in_executor(None, _warm_litellm)
716
739
  warm_future.add_done_callback(lambda _future: state.startup.mark("litellm_warmed"))
717
740
 
@@ -38,7 +38,13 @@ def _get_cli_commands() -> str:
38
38
  its internals, the CLI section will be omitted from the system prompt.
39
39
  """
40
40
  try:
41
- from dreadnode.app.cli.main import cli
41
+ from dreadnode.app.cli.main import cli, register_all_commands
42
+
43
+ # Subcommands import on demand (ENG-8259), and this listing is built
44
+ # once per process behind a cache. Under `dreadnode serve` nothing else
45
+ # would have registered them, so the agent's system prompt would name
46
+ # only the handful of commands defined in main.py, permanently.
47
+ register_all_commands()
42
48
 
43
49
  # Access internal command registry — guarded by try/except
44
50
  command_registry = getattr(cli, "_commands", None)
dreadnode/app/tui/app.py CHANGED
@@ -75,14 +75,24 @@ from dreadnode.app.tui.status_messages import STATUS_READY, STATUS_STARTING
75
75
  from dreadnode.app.tui.theme import (
76
76
  ACCENT,
77
77
  BG,
78
+ BG_LIGHT,
79
+ BG_LIGHTER,
80
+ BORDER,
81
+ BORDER_LIGHT,
78
82
  BRAND,
83
+ CODE,
84
+ CODE_BG,
79
85
  ERROR,
80
86
  FG,
81
87
  FG_FAINTEST,
82
88
  FG_MUTED,
83
89
  FG_SUBTLE,
84
90
  INFO,
91
+ IS_LIGHT,
92
+ LINK,
93
+ PURPLE,
85
94
  SUCCESS,
95
+ TEAL,
86
96
  WARNING,
87
97
  )
88
98
  from dreadnode.app.tui.turn_lifecycle import TurnLifecycle
@@ -1731,11 +1741,35 @@ class DreadnodeTextualApp(App[None]):
1731
1741
  warning=WARNING,
1732
1742
  error=ERROR,
1733
1743
  success=SUCCESS,
1734
- dark=True,
1744
+ dark=not IS_LIGHT,
1735
1745
  background=BG,
1736
1746
  surface=BG,
1737
- panel=BG,
1747
+ # $panel is the raised tier Textual paints its own widgets on
1748
+ # (DataTable headers, tooltips, collapsibles). Dark flattens it
1749
+ # onto BG; light needs the real step up, because BG there is
1750
+ # pure white and the surface would otherwise disappear.
1751
+ panel=BG_LIGHT if IS_LIGHT else BG,
1738
1752
  variables={
1753
+ "dn-bg": BG,
1754
+ "dn-bg-light": BG_LIGHT,
1755
+ "dn-bg-lighter": BG_LIGHTER,
1756
+ "dn-fg": FG,
1757
+ "dn-fg-subtle": FG_SUBTLE,
1758
+ "dn-fg-muted": FG_MUTED,
1759
+ "dn-fg-faintest": FG_FAINTEST,
1760
+ "dn-border": BORDER,
1761
+ "dn-border-light": BORDER_LIGHT,
1762
+ "dn-code": CODE,
1763
+ "dn-code-bg": CODE_BG,
1764
+ "dn-link": LINK,
1765
+ "dn-info": INFO,
1766
+ "dn-success": SUCCESS,
1767
+ "dn-warning": WARNING,
1768
+ "dn-error": ERROR,
1769
+ "dn-brand": BRAND,
1770
+ "dn-accent": ACCENT,
1771
+ "dn-purple": PURPLE,
1772
+ "dn-teal": TEAL,
1739
1773
  # Calm markdown headers — avoid primary orange bleeding in.
1740
1774
  # H1-H4 share $fg so headings read against $fg-subtle body
1741
1775
  # copy; H5/H6 step down to $fg-muted.
@@ -1,4 +1,4 @@
1
- /* Dreadnode TUI — minimal dark theme
1
+ /* Dreadnode TUI — minimal theme, dark by default
2
2
  *
3
3
  * Design principles:
4
4
  * 1. Content is king — full width for conversation
@@ -7,41 +7,44 @@
7
7
  * 4. Minimal chrome — one status line, no sidebar, no hotkey bar
8
8
  * 5. Clear hierarchy — bullets + whitespace over bordered panels
9
9
  * 6. Quiet confidence — subtle indicators, not flashy gradients
10
+ *
11
+ * Every variable below aliases a `$dn-*` theme variable that app.py feeds from
12
+ * theme.py, which resolves the dark or light value. Edit the palette there;
13
+ * a literal hex here would be correct for only one theme.
10
14
  */
11
15
 
12
- $bg: #17191c;
13
- $bg-light: #1f2226;
14
- $bg-lighter: #292c31;
15
-
16
- $fg: #f2f7fc;
17
- $fg-subtle: #c2c7cc;
18
- $fg-muted: #95999e;
19
- $fg-faintest: #696d72;
20
-
21
- /* Inline code + fenced blocks: near-white text on a raised background "pill".
22
- * The pill carries the "code" affordance; tune $code / $code-bg to restyle all
23
- * code. Aliased to the palette by design; set to a literal hex to diverge.
24
- * Keep in sync with CODE / CODE_BG in theme.py. */
25
- $code: $fg;
26
- $code-bg: $bg-light;
27
-
28
- /* Links: near-white + underline (the underline is the affordance). Warm accent
29
- * is reserved for tool calls. Keep in sync with LINK in theme.py. */
30
- $link: $fg;
31
-
32
- $border: #2b343f;
33
- $border-light: #434a55;
34
-
35
- $info: #4689bf;
36
- $success: #68c147;
37
- $warning: #c8ac4a;
38
- $error: #e44f4f;
39
-
40
- /* Extended palette — keep in sync with theme.py */
41
- $brand: #ca5e44;
42
- $accent: #ef562f;
43
- $purple: #a650fb;
44
- $teal: #20dfc8;
16
+ $bg: $dn-bg;
17
+ $bg-light: $dn-bg-light;
18
+ $bg-lighter: $dn-bg-lighter;
19
+
20
+ $fg: $dn-fg;
21
+ $fg-subtle: $dn-fg-subtle;
22
+ $fg-muted: $dn-fg-muted;
23
+ $fg-faintest: $dn-fg-faintest;
24
+
25
+ /* Inline code + fenced blocks: prose-colored text on a raised background
26
+ * "pill". The pill carries the "code" affordance; tune CODE / CODE_BG in
27
+ * theme.py to restyle all code. */
28
+ $code: $dn-code;
29
+ $code-bg: $dn-code-bg;
30
+
31
+ /* Links: always underlined, colored by LINK in theme.py. Warm accent is
32
+ * reserved for tool calls. */
33
+ $link: $dn-link;
34
+
35
+ $border: $dn-border;
36
+ $border-light: $dn-border-light;
37
+
38
+ $info: $dn-info;
39
+ $success: $dn-success;
40
+ $warning: $dn-warning;
41
+ $error: $dn-error;
42
+
43
+ /* Extended palette — platform screens, data visualization */
44
+ $brand: $dn-brand;
45
+ $accent: $dn-accent;
46
+ $purple: $dn-purple;
47
+ $teal: $dn-teal;
45
48
 
46
49
  Screen {
47
50
  background: $bg;
@@ -17,7 +17,7 @@ from textual.widgets import DataTable, Static
17
17
 
18
18
  from dreadnode.app.tui.screens.base import DreadnodeScreen
19
19
  from dreadnode.app.tui.spans_reader import RawSpansReader, SpanLineSummary
20
- from dreadnode.app.tui.theme import FG, FG_FAINTEST, FG_MUTED, FG_SUBTLE, WARNING
20
+ from dreadnode.app.tui.theme import FG, FG_FAINTEST, FG_MUTED, FG_SUBTLE, SYNTAX_THEME, WARNING
21
21
 
22
22
  if t.TYPE_CHECKING:
23
23
  from pathlib import Path
@@ -185,6 +185,6 @@ class RawSpansScreen(DreadnodeScreen):
185
185
  renderable = Group(
186
186
  self._detail_header(summary),
187
187
  Text(""),
188
- Syntax(payload, "json", theme="ansi_dark", word_wrap=True),
188
+ Syntax(payload, "json", theme=SYNTAX_THEME, word_wrap=True),
189
189
  )
190
190
  self.query_one("#raw-spans-detail", Static).update(renderable)
@@ -8,48 +8,71 @@ The palette is aligned with the variables in `dreadnode.tcss`.
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
+ import os
12
+
11
13
  # --- Color Palette -----------------------------------------------------------
12
- # Single source of truth. When changing values here, also update the matching
13
- # CSS variables in dreadnode.tcss (they cannot be auto-generated).
14
-
15
- BG = "#17191c"
16
- BG_LIGHT = "#1f2226"
17
- BG_LIGHTER = "#292c31"
18
-
19
- # Foreground ramp — even ~17 L* steps (perceptual lightness) on the dark BG so
20
- # each tier is equally distinguishable; all clear WCAG AA. FG is a crisp, faintly
21
- # cool near-white (L*97); the tail stays quiet for secondary/faint roles.
22
- FG = "#f2f7fc"
23
- FG_SUBTLE = "#c2c7cc"
24
- FG_MUTED = "#95999e"
25
- FG_FAINTEST = "#696d72"
26
-
27
- # Inline `code` and fenced blocks — near-white text on a raised background
14
+ # Single source of truth for both render paths. `dreadnode.tcss` declares its
15
+ # `$` variables as aliases of the `$dn-*` theme variables that `app.py` feeds
16
+ # from the constants below, so a value changed here reaches the stylesheet on
17
+ # its own. Never write a literal hex into the stylesheet — it would be correct
18
+ # for only one theme.
19
+
20
+
21
+ def _is_light() -> bool:
22
+ """Whether ``DREADNODE_TUI_THEME`` selects the light palette.
23
+
24
+ Any other value — unset, misspelled, or explicitly ``dark`` — selects dark.
25
+ """
26
+ return os.environ.get("DREADNODE_TUI_THEME", "dark").strip().lower() == "light"
27
+
28
+
29
+ # Read once at import. The palette is baked into the module constants below and
30
+ # into the Textual theme registered at app start, so it cannot change without
31
+ # restarting the process.
32
+ IS_LIGHT = _is_light()
33
+
34
+ BG = "#ffffff" if IS_LIGHT else "#17191c"
35
+ BG_LIGHT = "#f4f5f7" if IS_LIGHT else "#1f2226"
36
+ BG_LIGHTER = "#e8eaed" if IS_LIGHT else "#292c31"
37
+
38
+ # Foreground ramp — four tiers walking away from the background in even ~17 L*
39
+ # (perceptual lightness) steps, so each tier is equally distinguishable. Dark
40
+ # starts at a crisp, faintly cool near-white (L*97) and steps down; light
41
+ # mirrors that spacing upward from near-black. The top three tiers clear WCAG
42
+ # AA against BG in both themes; FG_FAINTEST is the quiet tail at roughly 3:1.
43
+ FG = "#1f2328" if IS_LIGHT else "#f2f7fc"
44
+ FG_SUBTLE = "#57606a" if IS_LIGHT else "#c2c7cc"
45
+ FG_MUTED = "#6e7781" if IS_LIGHT else "#95999e"
46
+ FG_FAINTEST = "#8c959f" if IS_LIGHT else "#696d72"
47
+
48
+ # Inline `code` and fenced blocks — prose-colored text on a raised background
28
49
  # "pill". The pill carries the "this is code" affordance, so the text stays in
29
50
  # the prose family instead of a saturated accent (which is reserved for tool
30
51
  # calls). Aliased to the palette by design; set to a literal hex to diverge.
31
- # Keep in sync with $code / $code-bg in dreadnode.tcss.
32
52
  CODE = FG
33
53
  CODE_BG = BG_LIGHT
34
54
 
35
- # Links — near-white text; the underline carries the affordance. Warm accent is
36
- # reserved for tool calls, so links stay quiet and hover to $accent.
37
- # Keep in sync with $link in dreadnode.tcss.
38
- LINK = FG
55
+ # Links — always underlined. Dark keeps them in the prose family so the
56
+ # underline alone carries the affordance; light needs a conventional blue,
57
+ # because near-black on white is indistinguishable from body copy. Warm accent
58
+ # is reserved for tool calls, so links stay quiet and hover to $accent.
59
+ LINK = "#0969da" if IS_LIGHT else FG
39
60
 
40
- BORDER = "#2b343f"
41
- BORDER_LIGHT = "#434a55"
61
+ BORDER = "#d0d7de" if IS_LIGHT else "#2b343f"
62
+ BORDER_LIGHT = "#afb8c1" if IS_LIGHT else "#434a55"
42
63
 
43
- INFO = "#4689bf"
44
- SUCCESS = "#68c147"
45
- WARNING = "#c8ac4a"
46
- ERROR = "#e44f4f"
64
+ INFO = "#0969da" if IS_LIGHT else "#4689bf"
65
+ SUCCESS = "#1a7f37" if IS_LIGHT else "#68c147"
66
+ WARNING = "#9a6700" if IS_LIGHT else "#c8ac4a"
67
+ ERROR = "#cf222e" if IS_LIGHT else "#e44f4f"
47
68
 
48
69
  # Extended palette — platform screens, data visualization
49
- BRAND = "#ca5e44"
50
- ACCENT = "#ef562f"
51
- PURPLE = "#a650fb"
52
- TEAL = "#20dfc8"
70
+ BRAND = "#bc4c34" if IS_LIGHT else "#ca5e44"
71
+ ACCENT = "#cf4b2f" if IS_LIGHT else "#ef562f"
72
+ PURPLE = "#8250df" if IS_LIGHT else "#a650fb"
73
+ TEAL = "#0b7d75" if IS_LIGHT else "#20dfc8"
74
+
75
+ SYNTAX_THEME = "default" if IS_LIGHT else "monokai"
53
76
 
54
77
 
55
78
  # --- Graphic (paste new art here; wordmark is auto-composed to the right) ---
@@ -31,6 +31,7 @@ from dreadnode.app.tui.theme import (
31
31
  FG_MUTED,
32
32
  FG_SUBTLE,
33
33
  LINK,
34
+ SYNTAX_THEME,
34
35
  WARNING,
35
36
  )
36
37
 
@@ -208,7 +209,7 @@ class ThemedMarkdown(Markdown):
208
209
  def __init__(
209
210
  self,
210
211
  markup: str,
211
- code_theme: str = "monokai",
212
+ code_theme: str = SYNTAX_THEME,
212
213
  justify: JustifyMethod | None = None,
213
214
  style: str | Style = "none",
214
215
  hyperlinks: bool = True,
@@ -48,7 +48,7 @@ Builds the evaluation request from CLI flags, an evaluation.yaml manifest (`--fi
48
48
  * `--concurrency` — Maximum concurrent evaluation samples.
49
49
  * `--task-timeout-sec` — Timeout per task in seconds.
50
50
  * `--max-steps` — Maximum agent tool/generation steps per sample. Enforced via a headless session policy; the task timeout still applies as a wall-clock bound.
51
- * `--cleanup-policy` — Sandbox cleanup policy. *\[choices: always, on\_success]*
51
+ * `--cleanup-policy` — Sandbox cleanup policy. Hosted E2B evaluations support only `always`; `on_success` is available on Docker and OpenSandbox deployments. *\[choices: always, on\_success]*
52
52
  * `--judge-model` — Override the judge model for all tasks in this evaluation.
53
53
  * `--env-model` — Override a task ENVIRONMENT (defender) model by role: ROLE=MODEL\_ID (e.g. —env-model blue=dn/claude-sonnet-4-6, repeatable). Distinct from —model (solver) and —judge-model. The role must be declared overridable in the task’s models map.
54
54
  * `--egress` — Additional outbound destination for this run’s sandboxes (e.g. —egress staging.example.com, —egress ‘\*.internal.corp’, —egress 10.20.0.0/16; repeatable). Targets union with the task’s own egress.allow and are checked against the deployment’s egress floor identically, so they grant no reach a task declaration could not. Supplying any target makes this run a declaration, restricting it to what is named plus the platform destinations the platform derives.
@@ -200,9 +200,9 @@ Terminal window
200
200
  $ dn evaluation retry <evaluation-id>
201
201
  ```
202
202
 
203
- Retry failed and errored samples in an evaluation.
203
+ Retry failed samples in an evaluation.
204
204
 
205
- Resets samples that ended in failed, timed\_out, or infra\_error back to queued so they are picked up by workers again.
205
+ Resets samples that ended in failed, timed\_out, cancelled, or infra\_error back to queued so they are picked up by workers again. Samples whose judge errored are not requeued: rerunning the agent would produce a different trajectory.
206
206
 
207
207
  **Options**
208
208
 
@@ -122,13 +122,12 @@ resources:
122
122
 
123
123
  sandbox:
124
124
  timeout_seconds: 1800
125
- workspace_mount: true
126
125
  exposed_ports:
127
126
  - 8080
128
127
  - 9229
129
128
  ```
130
129
 
131
- `cpu_cores` and `memory_mb` size the provider instance. `workspace_mount` controls whether your project workspace is mounted read-write. `exposed_ports` lists ports the platform should surface for host-side access. Defaults and valid ranges are in the [manifest reference](./manifest-reference.md).
130
+ `cpu_cores` and `memory_mb` size the provider instance. `exposed_ports` lists ports the platform should surface for host-side access. The deprecated `workspace_mount` field is accepted for existing manifests but ignored; the sandbox workspace is always a local directory. Defaults and valid ranges are in the [manifest reference](./manifest-reference.md).
132
131
 
133
132
  ## Outbound network scope
134
133
 
@@ -207,7 +206,6 @@ resources:
207
206
 
208
207
  sandbox:
209
208
  timeout_seconds: 1800
210
- workspace_mount: true
211
209
  exposed_ports:
212
210
  - 8080
213
211
 
@@ -89,7 +89,7 @@ The CLI resolves `selectors` into `secret_ids` before submitting the manifest. S
89
89
  | Field | Type | Default | Notes |
90
90
  | ----------------- | ------------ | ------- | -------------------------------------------------------------------------------------------------------------------------- |
91
91
  | `timeout_seconds` | integer | none | Sandbox expiry in seconds. Minimum 60, capped by your organization’s sandbox runtime limit. Omit for the platform default. |
92
- | `workspace_mount` | boolean | `true` | Mount the project workspace into the sandbox. |
92
+ | `workspace_mount` | boolean | `true` | **Deprecated.** Accepted for existing manifests but ignored; the sandbox workspace is local. |
93
93
  | `exposed_ports` | list of ints | `[]` | Ports to expose for host-side access. Must be 1–65535. Deduplicated and sorted. |
94
94
  | `egress` | object | none | Outbound scope for this runtime’s sandbox. Omitted and empty differ. See below. |
95
95
 
@@ -155,7 +155,6 @@ resources:
155
155
 
156
156
  sandbox:
157
157
  timeout_seconds: 1800
158
- workspace_mount: true
159
158
  exposed_ports:
160
159
  - 8080
161
160
  - 9229
@@ -7,7 +7,7 @@
7
7
 
8
8
  > How sandboxes isolate workloads, how to scope what an agent can reach on the network, and which controls remain yours.
9
9
 
10
- An agent in one hosted sandbox cannot read the memory, processes, or local filesystem of another sandbox. It can still make network requests to public services, including services exposed by another sandbox.
10
+ An agent in one hosted sandbox cannot read the memory, processes, or local filesystem of another sandbox. Standalone and interactive sandboxes can still make network requests to public services. Evaluation sandboxes use a narrower network boundary described below.
11
11
 
12
12
  This distinction matters when you run untrusted agent code: **compute isolation does not imply network isolation.**
13
13
 
@@ -17,34 +17,67 @@ Dreadnode runs each hosted sandbox in a separate E2B Firecracker microVM. Each m
17
17
 
18
18
  Agents may have broad privileges inside their own sandbox. They can run arbitrary commands, install packages, and modify local files. Those privileges stop at the microVM boundary.
19
19
 
20
- | Resource | Isolated between sandboxes? | Enforcement |
21
- | ----------------------- | --------------------------- | ------------------------------------------------------------ |
22
- | Memory and processes | Yes | Firecracker microVM |
23
- | Local filesystem | Yes | Separate microVM storage |
24
- | E2B project API | Yes | The E2B project API key stays in the Dreadnode control plane |
25
- | Dreadnode runtime API | Yes | A bearer credential scoped to one sandbox |
26
- | Public network services | No | Each service must authenticate its callers |
27
- | Outbound network | Governed by policy | An egress policy composed per sandbox — see below |
20
+ | Resource | Isolated between sandboxes? | Enforcement |
21
+ | ------------------------ | --------------------------- | ------------------------------------------------------------ |
22
+ | Memory and processes | Yes | Firecracker microVM |
23
+ | Local filesystem | Yes | Separate microVM storage |
24
+ | E2B project API | Yes | The E2B project API key stays in the Dreadnode control plane |
25
+ | Dreadnode runtime API | Yes | A bearer credential scoped to one sandbox |
26
+ | Evaluation task services | Yes | Restricted E2B ingress and paired-runtime egress rules |
27
+ | Public network services | No | Each service must authenticate its callers |
28
+ | Outbound network | Governed by policy | An egress policy composed per sandbox — see below |
28
29
 
29
- Evaluation agents and task environments also run in separate sandboxes. The agent cannot inspect the task environment’s files or processes. Dreadnode gives the agent only the public URLs declared by the task so it can interact with the intended services.
30
+ Evaluation agents and task environments also run in separate sandboxes. Dreadnode restricts each task environment’s public ingress and configures its paired runtime to reach only declared task service hosts plus the platform and model gateway. E2B adds the task sandbox’s traffic credential only when the paired runtime calls those exact hosts, including HTTP and WebSocket services.
31
+
32
+ ```
33
+ flowchart LR
34
+ Anonymous["Anonymous caller"]
35
+ API["Dreadnode API"]
36
+ Gateway["Model gateway"]
37
+ Internet["Other public hosts"]
38
+
39
+ subgraph Pair["E2B evaluation pair"]
40
+ direction LR
41
+ Runtime["Agent runtime sandbox<br/>deny-all egress"]
42
+ Task["Task sandbox<br/>traffic-token ingress"]
43
+ end
44
+
45
+ Anonymous -. "blocked" .-> Task
46
+ Runtime -->|"declared host + token header"| Task
47
+ Runtime -->|"allowlisted"| API
48
+ Runtime -->|"allowlisted"| Gateway
49
+ Runtime -. "blocked" .-> Internet
50
+
51
+ classDef sandbox fill:#171124,stroke:#a78bfa,color:#ececf0,stroke-width:2px
52
+ classDef allowed fill:#102018,stroke:#34d399,color:#d5ffe2,stroke-width:2px
53
+ classDef blocked fill:#2a0b10,stroke:#d40924,color:#ffd6d6,stroke-width:2px
54
+ class Runtime,Task sandbox
55
+ class API,Gateway allowed
56
+ class Anonymous,Internet blocked
57
+ style Pair fill:#08080b,stroke:#26262c,color:#ececf0
58
+ linkStyle 0,4 stroke:#d40924,stroke-width:2px
59
+ linkStyle 1,2,3 stroke:#34d399,stroke-width:2px
60
+ ```
61
+
62
+ Only the paired agent runtime gets deny-all egress by default. The task sandbox’s outbound traffic follows its egress declaration when present; otherwise it follows E2B defaults.
30
63
 
31
64
  ## Access to another sandbox
32
65
 
33
66
  A sandbox cannot enumerate or manage other sandboxes through the E2B project API because Dreadnode does not inject its E2B project API key into agent workloads. Dreadnode also checks the requesting user’s organization, workspace access, and sandbox ownership before allowing sandbox operations through the platform API.
34
67
 
35
- Network services are a separate boundary. E2B assigns public URLs to services running inside a sandbox. By default, anyone who knows one of these URLs can send requests to it.
68
+ Network services are a separate boundary. E2B assigns public URLs to services running inside a sandbox. By default, anyone who knows one of these URLs can send requests to it. Evaluation task sandboxes disable that default: anonymous requests fail, and E2B adds the provider traffic credential only when the paired runtime calls ports declared by the task.
36
69
 
37
- Dreadnode’s agent runtime API requires a bearer credential scoped to one sandbox. Only the sandbox owner can obtain it, and every client they attach presents the same value. Services supplied by a task, such as a web application or API under test, define their own authentication. If a task service has no authentication, another sandbox or internet client that learns its URL can request it.
70
+ Dreadnode’s agent runtime API requires a bearer credential scoped to one sandbox. Only the sandbox owner can obtain it, and every client they attach presents the same value. Services supplied by a task, such as a web application or API under test, define their own authentication when used outside an evaluation pair.
38
71
 
39
72
  Sandbox isolation is not service authentication
40
73
 
41
- Treat every service exposed from a sandbox as internet-reachable. Add authentication inside the service when it handles sensitive data or actions.
74
+ Treat services exposed from standalone or interactive sandboxes as internet-reachable. Evaluation pairing does not change those workloads.
42
75
 
43
- E2B supports traffic access tokens for restricting public sandbox URLs. Dreadnode does not currently expose this as a customer-configurable setting. See E2B’s [public access controls](https://docs.e2b.dev/network/restrict-public-access.md) for the provider capability.
76
+ Dreadnode applies E2B traffic access tokens internally to evaluation task sandboxes. This control is not customer-configurable. See E2B’s [public access controls](https://docs.e2b.dev/network/restrict-public-access.md) for the provider capability.
44
77
 
45
78
  ## Internet and private networks
46
79
 
47
- By default an agent reaches the public internet. It can download packages, clone repositories, call public APIs, and send data to external hosts.
80
+ By default an agent reaches the public internet. It can download packages, clone repositories, call public APIs, and send data to external hosts. Evaluation runtimes instead use deny-all egress with exact allowances for their task services, the Dreadnode API, and the model gateway.
48
81
 
49
82
  Hosted sandboxes additionally have private and link-local ranges blocked at E2B’s network edge — `10.0.0.0/8`, `100.64.0.0/10`, `127.0.0.0/8`, `169.254.0.0/16`, `172.16.0.0/12`, and `192.168.0.0/16` — which keeps agents off common private-network and cloud metadata addresses. It restricts no public destination.
50
83
 
@@ -157,17 +190,17 @@ On hosted sandboxes, enforcement rides the sandbox create call and is always in
157
190
 
158
191
  Dreadnode limits the credentials available inside each sandbox:
159
192
 
160
- | Credential or data | Control |
161
- | ---------------------------- | --------------------------------------------------------------------------- |
162
- | E2B project API key | Held by the Dreadnode API and never injected into agent sandboxes |
163
- | Evaluation and job API keys | Restricted to the platform scopes required by that workload |
164
- | Interactive runtime API keys | Follow the initiating user’s platform permissions |
165
- | Runtime bearer credential | Unique to one sandbox; required by the Dreadnode runtime API |
166
- | Workspace storage | Uses temporary credentials limited to one organization and workspace prefix |
167
- | Model gateway key | Limited to permitted models, expires, and cleanup requests revocation |
168
- | User secrets | Injected only when selected for the workload |
169
-
170
- Processes inside a sandbox can read credentials and secrets injected into that sandbox. Wherever public egress remains available, compromised or malicious code can send those values to an external host. Inject only the secrets the workload needs, use the narrowest permissions available, and declare an egress scope when the workload’s destinations are knowable — it bounds where a leaked secret can go.
193
+ | Credential or data | Control |
194
+ | ---------------------------- | ------------------------------------------------------------------------------------------- |
195
+ | E2B project API key | Held by the Dreadnode API and never injected into agent sandboxes |
196
+ | Evaluation and job API keys | Restricted to the platform scopes required by that workload |
197
+ | Interactive runtime API keys | Follow the initiating user’s platform permissions |
198
+ | Runtime bearer credential | Unique to one sandbox; required by the Dreadnode runtime API |
199
+ | SDK remote storage | Fetches temporary workspace-scoped credentials on demand; provisioning does not inject them |
200
+ | Model gateway key | Limited to permitted models, expires, and cleanup requests revocation |
201
+ | User secrets | Injected only when selected for the workload |
202
+
203
+ Processes inside a sandbox can read credentials and secrets injected into that sandbox. A paired evaluation runtime can send them only to its allowlisted hosts. Wherever public egress remains available, compromised or malicious code can send those values to an external host. Inject only the secrets the workload needs, use the narrowest permissions available, and declare an egress scope when the workload’s destinations are knowable — it bounds where a leaked secret can go.
171
204
 
172
205
  ## Lifetime and audit trail
173
206
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  > Every file Dreadnode writes under ~/.dreadnode/ — config, profiles, transcripts, spans, caches, and auth tokens.
9
9
 
10
- Dreadnode keeps all local state under `~/.dreadnode/`. Nothing ships to the platform that isn’t explicitly sent; nothing sensitive sits outside this directory. Back it up and you back up every session, every profile, every cached artifact.
10
+ Dreadnode keeps its local state under `~/.dreadnode/`. Managed sessions also sync the state needed by the platform, while credentials and local caches remain in this directory. Back it up and you back up every local session, profile, and cached artifact.
11
11
 
12
12
  ```
13
13
  ~/.dreadnode/
@@ -69,8 +69,8 @@ Caution
69
69
 
70
70
  Don’t check `~/.dreadnode/` into version control. `config.yaml` and `mcp-auth.json` contain credentials.
71
71
 
72
- ## Sandbox mount
72
+ ## Storage in managed sandboxes
73
73
 
74
- When a Dreadnode-managed sandbox runs, `~/.dreadnode/` inside the sandbox is mounted via `s3fs` to the workspace’s storage bucket — scoped to `{org_id}/workspaces/{workspace_id}/`. Writes from the sandbox land in the same logical tree, but the physical storage is the platform, not the sandbox’s disk.
74
+ Inside a Dreadnode-managed sandbox, `~/.dreadnode/` and `/home/user/workspace` are local to that sandbox. They are not object-storage mounts. Pausing and resuming an E2B sandbox preserves its local filesystem because it resumes the same sandbox.
75
75
 
76
- This is what lets a session’s transcripts and artifacts survive when the sandbox is reset or replaced.
76
+ Resetting, replacing, or deleting a sandbox discards files that were not synced elsewhere. Managed session state is synchronized through the platform API, and SDK remote-storage operations access object storage explicitly.
dreadnode/core/tls.py CHANGED
@@ -41,7 +41,7 @@ _GENERATED_MARKER = "# dreadnode-generated trust bundle (system + operator CA)"
41
41
  # CURL_CA_BUNDLE ............... curl
42
42
  # GIT_SSL_CAINFO ............... git
43
43
  # NODE_EXTRA_CA_CERTS .......... Node, Bun, Claude Code CLI, agent-browser
44
- # AWS_CA_BUNDLE ................ botocore, s3fs, mount-s3
44
+ # AWS_CA_BUNDLE ................ botocore, Python s3fs
45
45
  # GRPC_DEFAULT_SSL_ROOTS_FILE_PATH ... the OTLP span exporter
46
46
  TRUST_FILE_ENV_VARS: tuple[str, ...] = (
47
47
  "SSL_CERT_FILE",
@@ -68,8 +68,9 @@ class Storage:
68
68
  job.json ← terminal-only frontier hashes
69
69
  ```
70
70
 
71
- When running in a sandbox, ~/.dreadnode is mounted via s3fs to the user's
72
- workspace storage, with the S3 prefix already scoped to {org_id}/workspaces/{workspace_id}.
71
+ Remote operations use an fsspec-compatible object-storage client. In a
72
+ managed sandbox, the local cache remains on the sandbox filesystem; it is
73
+ not an object-storage mount.
73
74
  """
74
75
 
75
76
  def __init__(
@@ -419,7 +420,7 @@ class Storage:
419
420
  return self.cas_path / algo / hash_val[:2] / hash_val[2:4] / hash_val
420
421
 
421
422
  def remote_blob_path(self, oid: str) -> str:
422
- """Remote path for blob (includes bucket for s3fs)."""
423
+ """Remote path for blob, including the provider bucket."""
423
424
  algo, hash_val = oid.split(":", 1)
424
425
  bucket = self._credentials.bucket if self._credentials else "user-data"
425
426
  return f"{bucket}/{self.remote_prefix}/cas/{algo}/{hash_val[:2]}/{hash_val[2:4]}/{hash_val}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dreadnode
3
- Version: 2.0.44
3
+ Version: 2.0.45
4
4
  Summary: Dreadnode SDK
5
5
  Project-URL: Homepage, https://dreadnode.io
6
6
  Project-URL: Documentation, https://docs.dreadnode.io
@@ -29,7 +29,7 @@ dreadnode/agents/mcp/client.py,sha256=13sEJhLDfk1lPeTXfOsoc2j_akX1WX4rc4PSp_Cw36
29
29
  dreadnode/agents/mcp/config.py,sha256=hBROQZqUF4Y9wI6aLpYc5wHjneIA593PjfHDO1voAN0,2433
30
30
  dreadnode/agents/mcp/server.py,sha256=LyEgY7xvAwMZG3ioMUwfPsP0zSAlKeXtgU2XearDx_0,3358
31
31
  dreadnode/airt/__init__.py,sha256=VMUzuBuzQdsKYkPs5OLyINCgfLX9Rrr0XQpObEYofp8,10589
32
- dreadnode/airt/_base.py,sha256=avHEpRPaa1GzCWBXD6YPtXuZ0iPzTDrjzcPDhiS8QaU,8476
32
+ dreadnode/airt/_base.py,sha256=qb2jsTOsDtwZg7u5DndyjGSYZLsE8pAlQ7cLuj9QJYg,11567
33
33
  dreadnode/airt/adversarial_reasoning.py,sha256=pDTIDIHRF_DL0YjEGmSeEtPGKCltjuFlAEIFlKOqqmU,13630
34
34
  dreadnode/airt/advpromptier.py,sha256=TJPPUdUTJu2nnY5VJKNapzDcClBHI49yU9HSSV4R2Ik,10062
35
35
  dreadnode/airt/agent_suite.py,sha256=alR7iuUi9Pa2HH9p8OdbMj-bkGFHrnqcr-slVx-_Rao,8180
@@ -203,10 +203,10 @@ dreadnode/app/cli/args.py,sha256=MSHhdpCHfhvlSYfP6tEyPnNBH9i_TkEbvpV8RZ8vrs4,874
203
203
  dreadnode/app/cli/capability.py,sha256=H4sowggKHKw1HRkFP3OzHOYPy6n1yKraiAGO8rSfvos,56034
204
204
  dreadnode/app/cli/dataset.py,sha256=cv5xNxBO0SihNmQauLJxIpZzNFl1sKxP8GIRKmCulwI,14023
205
205
  dreadnode/app/cli/environment.py,sha256=Slm_4d9VuzbhuzC3Pino2n5C6orKG7riT4E58Lx-5v8,17054
206
- dreadnode/app/cli/evaluation.py,sha256=mlkwGuHZPZ7M4tA2WeYIlooqiSLtW5p1E0Rk3KA203A,68911
206
+ dreadnode/app/cli/evaluation.py,sha256=rUTGwBTBZG-Prh5PzK2-hbceCV4Tk3dj_5mRUxstA98,69050
207
207
  dreadnode/app/cli/inference_model.py,sha256=W9zxwlspvSr2r1MoaTCuY8K2wEoedugPdhZHKq1_QfM,4707
208
208
  dreadnode/app/cli/judge.py,sha256=aqI5jL4xhCti9ZJmZ14ihhDgKj_RJsfiG5WjhllVJnU,15864
209
- dreadnode/app/cli/main.py,sha256=u7n5ukMcEmPNgfuI26ltS9TlcmorJuE0VwPx-XfOIbI,22002
209
+ dreadnode/app/cli/main.py,sha256=ObfPdqZcL58KK_aOSdVnNLKQIGrG5A9ONzACHTTRS8Y,26964
210
210
  dreadnode/app/cli/model.py,sha256=A4kWIRLWm26B296McNWM1WEpDqCFGoxgOirCoNy66Tw,17372
211
211
  dreadnode/app/cli/optimize.py,sha256=qQ8YnNFG3CCgrqZw4MiS9d_8BOsTwIjw7WtqDapcqPU,22788
212
212
  dreadnode/app/cli/runtime.py,sha256=MOtCGndZ5QivE3LKgkhUriVMk2ma-wiVqOszPGUZl_8,37348
@@ -235,11 +235,11 @@ dreadnode/app/client/models.py,sha256=TbzXwrpoog3oGWpgbpZ2-JoTaxBuSQx1DDh4GSpsfb
235
235
  dreadnode/app/client/runtime_client.py,sha256=KXX-z1PjNm-2GCapoVPpUz--aLAA2Piu03Ykj_lHRlY,55714
236
236
  dreadnode/app/client/transports.py,sha256=SOUX_tMNhoMPPNnk_6HRoyPNGeeDMn1KhnrdlS2d304,15921
237
237
  dreadnode/app/server/__init__.py,sha256=fub75X9MOi9mK_-vFPD03rNMC04gYsNkecVSPbsI4Ic,187
238
- dreadnode/app/server/app.py,sha256=vsg-g7mDj5QfZhkI_-Bj4h-1oZtxoJ4LSafzTzyg510,216918
238
+ dreadnode/app/server/app.py,sha256=u1nG7X8VOrohd6uI0u4YhVoyDiMVsH-5j-gXU4irQaI,218039
239
239
  dreadnode/app/server/auth.py,sha256=LVQ6lgSc74Y3sWRGM2pf6Xgp2n7c6oWQZZPJ6k6PjnY,4440
240
240
  dreadnode/app/server/capability_manager.py,sha256=91FJYQ8RIQAkRRFCoCnUbQc89Y0ghSOAJ3-PGVaJUv4,52428
241
241
  dreadnode/app/server/model_resolution.py,sha256=LwahZgnaToC6kOePylLJCzCN1ajTj47YxIceo2syAa4,4735
242
- dreadnode/app/server/prompt.py,sha256=Xg2NfT3ymGJHb0m1YuK4NJejOpQkYfdIXX-RAdY_CyE,12255
242
+ dreadnode/app/server/prompt.py,sha256=_1Q9G8i-KlfNnApK4mK_2_nqrMDOZb0-49vS0kaS1GY,12618
243
243
  dreadnode/app/server/prompt_registry.py,sha256=tap9N9vAzQk0BHhECCHr3JKrLX3oMhzF14X4rH-Rur4,6805
244
244
  dreadnode/app/server/runtime_credentials.py,sha256=Zo0EfulHxTSycOhoYE4O0gKXwMdCKCMSyn1RkfEB_FY,11744
245
245
  dreadnode/app/server/runtime_events.py,sha256=a4b2iuKjJaly2pcDxVs7wHXZ2uHe1snSW3H0lS4k9pg,17792
@@ -251,14 +251,14 @@ dreadnode/app/server/websocket.py,sha256=PEUI5gYofWW-t5sQd-DvKps6ezD9tSFSWDG_6MR
251
251
  dreadnode/app/server/worker_manager.py,sha256=krmTM_asIp3SB91iuD3DlrQm0S62k9LMh-igDG1kyAI,28219
252
252
  dreadnode/app/server/ws_auth.py,sha256=n1Bo91yM6IFQHNZy3j-r6RiCjzA44205q9cQxf9_iLU,3991
253
253
  dreadnode/app/tui/__init__.py,sha256=DwG6Q1FhmmFcxQ75ziJFjj940dtPeR3J6rilSCOe5WE,64
254
- dreadnode/app/tui/app.py,sha256=eppjlolR3mLiW4t6mqOg_8FYiUvOX-FmH3BmbxO3Hw8,198695
254
+ dreadnode/app/tui/app.py,sha256=4K9UCIP7Mp9wIj5CklbGmyeCC5aOYeQKPGCAIUr10aI,199999
255
255
  dreadnode/app/tui/auth_flow.py,sha256=ufU7c9lmGUc80ewzw9CJo8riahQjqAt2pKjccH39-74,6872
256
256
  dreadnode/app/tui/bug_report.py,sha256=AtfFhdeddnvA8OtCFBd90U2TVAwH2nlKYm3X6cbwMcY,17696
257
257
  dreadnode/app/tui/capabilities_manager.py,sha256=bft9Q2tvDkFQQBuqAfVYfrC-VZ2_tbi2vUBYZRXZLzg,22231
258
258
  dreadnode/app/tui/command_dispatcher.py,sha256=zO8Rwa4-96ySO5h1s3hwLiGf68CYl22jjOQgBt_H9ag,32942
259
259
  dreadnode/app/tui/commands.py,sha256=PwLg5CdNoaEJwI27HIw5nlfqsdfpSyjf9HSya5aChfA,4085
260
260
  dreadnode/app/tui/connection.py,sha256=Ca4tXPOjjYxM5eIjTgXQTHAqLlOj8vCCFoRYn7KwkL8,12676
261
- dreadnode/app/tui/dreadnode.tcss,sha256=O0LBXcMhBguB97cL5gSKOFooyAVqvVthL6ZxEJgA88A,19758
261
+ dreadnode/app/tui/dreadnode.tcss,sha256=g-ZAKkACc3ns7IA6yDOZqGozkDToEvAghlxXLQ-bACs,19923
262
262
  dreadnode/app/tui/error_handler.py,sha256=qcZLaLgBNbyf540Yl5BYFxcZXBnaeDKI8cB81waOnFU,7074
263
263
  dreadnode/app/tui/model_manager.py,sha256=t0I52eq9ks-mk2ui7Oldn0Cl7jZauJMSqe6EIbfxlZw,36548
264
264
  dreadnode/app/tui/model_variants.py,sha256=e8_zTTZLUCtAM_BR8QDDHeV3SswqoFHLBPvThrKSIaE,8835
@@ -267,7 +267,7 @@ dreadnode/app/tui/screen_router.py,sha256=Xn7UKPAt0J2SoXqpFcTiSjwXRd8VM1uzgoG4T2
267
267
  dreadnode/app/tui/sessions_manager.py,sha256=nSX7hmzC5Ga9qNYkuho5BV39x4X6_1tUyNAwYoho4sc,91973
268
268
  dreadnode/app/tui/spans_reader.py,sha256=0jsdGBsTTt2uektvsyQH1Wl-_uW2blRsamOy9HnXmRg,4201
269
269
  dreadnode/app/tui/status_messages.py,sha256=fr4wcNl0VbgsNAQkLGCewMaFpoP1S8IS33w599fYog8,426
270
- dreadnode/app/tui/theme.py,sha256=h2vNnTF4YQdASKB99MvhOQpMqYpidqdJPWHoJl9lmIA,5617
270
+ dreadnode/app/tui/theme.py,sha256=UQ9diWF-6RhPvrTQJF-bQJBkyXZVWk17xbX8jPRenSQ,7040
271
271
  dreadnode/app/tui/tool_format.py,sha256=qdJ0jz0aIL2c6_aRVKXBrfVqWExAhr3kL-BrgJNZfU8,22081
272
272
  dreadnode/app/tui/turn_coordinator.py,sha256=JUHs-za7zGLCgWEgt_nf080t-rJ5qK0CH_jPy0fJRaw,26812
273
273
  dreadnode/app/tui/turn_lifecycle.py,sha256=K_QnfRccQC9nFEhsMwbS_cq3wz0QMEKJ0O8d7tuvDAc,7716
@@ -285,7 +285,7 @@ dreadnode/app/tui/screens/console.py,sha256=UfSnixGXOkP4w0gPfYMOTu0dYsSy7Ftli9-M
285
285
  dreadnode/app/tui/screens/environments.py,sha256=thEKsMXCvD9gSpQS0ScrTAE_wgNsvr6DDYOBnW-Bd5A,18646
286
286
  dreadnode/app/tui/screens/evaluations.py,sha256=nlmllhQA2NRPjIie4oCy3wswuUytEoTtRXd-I0Gg2KA,20441
287
287
  dreadnode/app/tui/screens/models.py,sha256=u_Dv4W5GhKtLM6YPc8HiYtwNNGlJBAAELRkDac75SgE,38395
288
- dreadnode/app/tui/screens/raw_spans.py,sha256=JIntXgq7tLIL6w7n1n8XjP3vqEMqFfxUmK7pOmcW7N4,7148
288
+ dreadnode/app/tui/screens/raw_spans.py,sha256=62fq45xUNO1Fsfw6VTS5RVbwOs-Ug9phsek41K0LOMg,7163
289
289
  dreadnode/app/tui/screens/readme.py,sha256=wEaBZhTE7FmA6T4MMEeGvxYDIOFfV-SV0K7mPnHK6wU,3684
290
290
  dreadnode/app/tui/screens/report_bug.py,sha256=lPs7xsXPwREv8jYY00KQ4wL85C55j1cNEBb8MT9VQ80,16784
291
291
  dreadnode/app/tui/screens/runtimes.py,sha256=zBZQ2vFnLmaH_8c9QChQmPJSi7NJ95bnwSDI7WSK_AI,51540
@@ -321,7 +321,7 @@ dreadnode/app/tui/widgets/skills_dialog.py,sha256=ykIPJfquX6eVTScnhyrQiZ2obe2eZB
321
321
  dreadnode/app/tui/widgets/slash_overlay.py,sha256=KFNrosL8lmzc3BdJ0kWAKO8ByUTJTmWCFA2jAicCnDQ,3522
322
322
  dreadnode/app/tui/widgets/status_bar.py,sha256=eEH4isKvMaVtbIVorPnAS14zXhXkMkiqmfbl5tWoS0I,4799
323
323
  dreadnode/app/tui/widgets/throbber.py,sha256=NbgzuL86GgPnNS5P-X07ZanACkqwFksH1ELg-UbVzAs,2428
324
- dreadnode/app/tui/widgets/tool.py,sha256=dsr2oI6Xs_d1qc0TgvbgHd6w8K6oj49Q1LIaJGtPL2k,25095
324
+ dreadnode/app/tui/widgets/tool.py,sha256=kw0x9ibh9kkQQAIM8sO-F-Y-fgw1dRlnaCeP0SpNQLI,25116
325
325
  dreadnode/app/tui/widgets/tool_progress.py,sha256=VYr-vpC5214DgIAxrG8elONbTO1KHbIKYBzpmiCh_TM,3682
326
326
  dreadnode/app/tui/widgets/tools_dialog.py,sha256=KvwObbYvT2yRpYXcIpYHR_Jx9Vo3rCAYy_4Solra_y4,2096
327
327
  dreadnode/app/tui/widgets/welcome.py,sha256=-XI4BcV1uQBGAGootUGunmU7fb1Krg065zzph23tDM4,15731
@@ -370,7 +370,7 @@ dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/air
370
370
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/capability.md,sha256=SKOsUhcLv_Ou3YKp3oJvN9zp4hsHRJOMJ6aMpwqV03I,10131
371
371
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/dataset.md,sha256=dUDXFotOLmYjouQu7tKI1GJ0h_bNyHSkyo4mVmvKiTY,4871
372
372
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/env.md,sha256=rSvZJYykjxREeumyA3656yGMqxI7O6SjcJZGbsPdQXY,4542
373
- dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/evaluation.md,sha256=irCm3NYx_sO6GBvaNnJmCTeRrbYBOYwxPPg7uvhZurc,10901
373
+ dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/evaluation.md,sha256=IQ3-GxjOXgMx79MJd5wLzfkDCyz-Ex5olCxQF9yuvro,11115
374
374
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/inference-model.md,sha256=tyZifFBzlYFXCNkAk2HIFu828mc62LOBRUEuAr_-FBs,1657
375
375
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/judge.md,sha256=-pX8CtXpAytZWaBekbKR8GUi9qlnCfdl5FyXrwhVrgo,2023
376
376
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-cli/references/cli/main.md,sha256=2AG506lKzD7N9Zh5BqqE9U0xO3_A0s9XM2XCihq9ylE,2338
@@ -401,18 +401,18 @@ dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/pl
401
401
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/platform/secrets.md,sha256=r2pSuwamAHmg7oasdlad32I2LmBFXCYYxWOeEipsquo,7094
402
402
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/platform/settings.md,sha256=lpxTXDWP0_Yq_JS9a3j_CTE7Qn5G00E8-ZaFj6eYshg,11346
403
403
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/platform/workspaces.md,sha256=srQeAmPoSnzW0lTAHdioBwbYseW04sKnLULkNePuqCQ,4722
404
- dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/configuration.md,sha256=7DK_0A0UA54NNAorRouuB9GHC_eCPEAH2lVCOPkkcVc,8462
404
+ dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/configuration.md,sha256=E-Onx_TN7CfoIIjMsyffQufrXFjgvPXPc-xKAiCEhEQ,8471
405
405
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/managing.md,sha256=geUNFZ2xNgjVOXqlSyKS0vZxIAQDmcTOmwMZBhE_AdU,8446
406
- dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/manifest-reference.md,sha256=tkbWRd_bGX3CVsjNeeEDlmoUimuf5BrJRrColQqpNd8,10333
406
+ dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/manifest-reference.md,sha256=imaGIjpTXAcO7Yrz8eqpLoTz9tOlg0e1g7O8QJz9GO0,10309
407
407
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/overview.md,sha256=g_zWSUBrLcnzQyq8SyEKPgGBT3GSKigK_sKJtAVEuTA,4447
408
408
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/quickstart.md,sha256=57MDx0gEI_dtXHCLcCigiQJgwtC4eoEDEPW1cECBgFM,3364
409
409
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/runtimes/serve.md,sha256=INt3vyDxpIwRdjKQEH4oaLtKK32VnWMa0D66oqZ56oM,4628
410
410
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/sandboxes/environment-lifecycle.md,sha256=SQ4B822MCdBib6L6mrvyCvPWO_3RE1uTiCZAyT8aYmA,5491
411
411
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/sandboxes/inspecting.md,sha256=HWyXLphy3JgLR90khcxEvGOc4pGeQ6OGjbwo2bSzMvY,4478
412
- dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/sandboxes/isolation.md,sha256=sGrTu27Evg51iKvl3Y-jMZbu6D75gfTA9Qedya-sPpg,15592
412
+ dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/sandboxes/isolation.md,sha256=0g1tlPxmSIdSGP_10K4mPinBuEjY_GafShD9fOETkjQ,17503
413
413
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/sandboxes/overview.md,sha256=Mwu6cArpkSvDDnMaDVO7XdNSPuifcu_fhLYLAe9mjhk,5847
414
414
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/sandboxes/runtime-limits.md,sha256=YWjoUzk2ntRObTVuuT8jC3Cm_N05dv9zydcoO6Xxf2E,2206
415
- dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/tui/local-storage.md,sha256=yHPpND3_TwvXC9OQT6Ne0mWRoVfX7JfkF6zz-d2Flmw,6256
415
+ dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/tui/local-storage.md,sha256=IZk9FSVzqKNEIQqtZtajIdRReJUc79DW0fLvn07Lo8k,6340
416
416
  dreadnode/builtin_capabilities/dreadnode/skills/dreadnode-concepts/references/tui/overview.md,sha256=GL9i-vPrWWKFlPZsH2x5jHqq4YB1Rt83QDQ_NBi-SGM,3048
417
417
  dreadnode/builtin_capabilities/dreadnode/skills/getting-started/SKILL.md,sha256=P8Bl8n0-NQZwzfzNy79uUX5ycxZW-XR6gij3GdP-NGE,13030
418
418
  dreadnode/builtin_capabilities/dreadnode/skills/research-capabilities/SKILL.md,sha256=Dc-kHc_BDUAaCbFy3UyRbvoM-GF8CX8umIblj2AsApg,4871
@@ -458,7 +458,7 @@ dreadnode/core/startup_clock.py,sha256=aJXDPJYx1WZrl6PFS9JIcoZT1PNDxvxLNEtkFeIAo
458
458
  dreadnode/core/stopping.py,sha256=cm4b1hbOT731gZLptLAMzwlO13LmjHc1c-r9lAls8sw,11889
459
459
  dreadnode/core/task.py,sha256=FnXjYUBWhxcL5qcLkg79tZv4CtPspvr_aLfXltce92g,32758
460
460
  dreadnode/core/templating.py,sha256=djDbkOEQoQNf3FFE7vSEihjVviaPkl6HdAk0ZkoTbY0,4653
461
- dreadnode/core/tls.py,sha256=NUHSjfBTyjwvKU2PIzAQ99KlQP6Y8HsweTRgAY6M3us,15092
461
+ dreadnode/core/tls.py,sha256=fyqqjfvyuQyfRO1Nhh7PYGv-MiyyZIzyAf1sG0FQggE,15089
462
462
  dreadnode/core/transforms.py,sha256=wuJLZzUTr1PzT3tTlOdf4LQvDgk18t20sUYo2TVkaTs,13248
463
463
  dreadnode/core/util.py,sha256=z9zxwVcmzOgXzOEVsuEJQKaOnuKukua2MtaJGYz_4oY,22683
464
464
  dreadnode/core/meta/__init__.py,sha256=P4kVnPufWQcetysMULq37Ghpwi1FcjwQ-BOE9gI3mHU,1325
@@ -601,7 +601,7 @@ dreadnode/scorers/supply_chain_detection.py,sha256=QyS53yYSPXTiV-lGRJy7G7eJBYbPM
601
601
  dreadnode/storage/__init__.py,sha256=4iiLn_vhBqpNlfuTHs1fWY-It-hqJIF7mPUqg_7Dlk4,1201
602
602
  dreadnode/storage/providers.py,sha256=BmtHZcfxZRf741k0DFiNmv0zryf5rYs7nGK9jHiAg2Q,7422
603
603
  dreadnode/storage/session_store.py,sha256=HZC8l0TJ6I42PwvLKGWLJNBJqvj8UGTYBqhSEpx-0Yc,18498
604
- dreadnode/storage/storage.py,sha256=WA3YGLc_uOwmE2RdjzhDXKNl9-_DhtOQ_UNOd1jUFls,34104
604
+ dreadnode/storage/storage.py,sha256=Wac7h0n-chy14zSKR_8M8xD0Ni3hhPHkSvb7T4JccvI,34120
605
605
  dreadnode/tools/__init__.py,sha256=a3TLVpCFiS2VwvRMN9U_z46SESMxbLOBjpl31Kqev30,4594
606
606
  dreadnode/tools/_ripgrep.py,sha256=GVIzsfl6DkFR9GwohIJS1ybi5UiZwD-jjiD0M_tnfmU,1465
607
607
  dreadnode/tools/apply_patch.py,sha256=RjzzB2CJ2-9EcBi2iD7qYunzN8hk4amACw7nsBOiFIQ,14880
@@ -736,8 +736,8 @@ dreadnode/transforms/system_prompt_extraction.py,sha256=rtWKibIipr1tmFH3P95UE4W4
736
736
  dreadnode/transforms/text.py,sha256=8qEldTVeXblxxCCU31Q2MGx3dccMxESOoSga-Y87Mnk,19923
737
737
  dreadnode/transforms/video.py,sha256=pWPSAN3YokvWpYsAOvOVH1d2Zd5JPldl17ICw1532a8,33136
738
738
  dreadnode/transforms/xml_tools.py,sha256=Zj2sdvmyCoVLua4XkWnKYFRmS8ZFSWrQ5nunspCqpM4,15057
739
- dreadnode-2.0.44.dist-info/METADATA,sha256=iyNjHCnxsq76F0YpnX58hzFg--plN5A4aUE96eexK_c,13895
740
- dreadnode-2.0.44.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
741
- dreadnode-2.0.44.dist-info/entry_points.txt,sha256=LvV-eGt7GlPEHy1YDiClWhr4qbsX_D9l0_QNo0j_XPI,81
742
- dreadnode-2.0.44.dist-info/licenses/LICENSE,sha256=UMJAkT3OxyPBvOmQ74h_1v8DYFRYIfMfFiaP2lFZCmc,11375
743
- dreadnode-2.0.44.dist-info/RECORD,,
739
+ dreadnode-2.0.45.dist-info/METADATA,sha256=B2zm-2VUBbZw-RTfYROIRIalGtULTzC-SqQG3gGisgE,13895
740
+ dreadnode-2.0.45.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
741
+ dreadnode-2.0.45.dist-info/entry_points.txt,sha256=LvV-eGt7GlPEHy1YDiClWhr4qbsX_D9l0_QNo0j_XPI,81
742
+ dreadnode-2.0.45.dist-info/licenses/LICENSE,sha256=UMJAkT3OxyPBvOmQ74h_1v8DYFRYIfMfFiaP2lFZCmc,11375
743
+ dreadnode-2.0.45.dist-info/RECORD,,