diff --git a/mkdocs/docs/concepts/presets.md b/mkdocs/docs/concepts/presets.md index e091dd28f..36a639622 100644 --- a/mkdocs/docs/concepts/presets.md +++ b/mkdocs/docs/concepts/presets.md @@ -122,7 +122,7 @@ Alternatively, pass `--fleet` to `dstack apply`. === "Base" - Set `base` to let the creation agent select any compatible variant of the base model, including a different precision, quantization, or trusted fork. + Set `base` to let the agent select any compatible variant of the base model, including a different precision, quantization, or trusted fork. ```yaml base: Qwen/Qwen2.5-7B-Instruct @@ -240,6 +240,24 @@ Submit the run dsv4-flash? [y/n]: y ## Manage presets +### Watch presets + +While a preset is being created, you can watch the progress of its trials and what the agent is doing. + +The `dstack preset logs` command shows the progress log: one line per milestone, such as a trial finishing or the final service being verified. Pass `-f` to follow a running creation: + +
+ +```shell +$ dstack preset logs -f c83375b4 +``` + +
+ +### Traces + +The agent subprocess writes real-time traces to `~/.dstack/presets//trace.jsonl`: the agent's messages and every tool call with its result. Traces are the main way to analyze a session in depth — see [Protips](#protips). + ### List presets Use `dstack preset` to list presets: @@ -290,19 +308,14 @@ $ dstack preset delete c83375b4 !!! info "Reference" For command options and agent settings, see the [`dstack preset` CLI reference](../reference/cli/dstack/preset.md). -## Troubleshooting +## Protips -To trace the agent's activity, pass `--debug` to `dstack apply`: - -
+Under the hood, presets run an agent as a subprocess, using the local `claude` CLI. This process writes a real-time trace to `~/.dstack/presets//trace.jsonl`. The subprocess is launched with a built-in harness: how to run trials, submit runs, benchmark, verify presets, and use `dstack`. -```shell -$ dstack apply -f preset.dstack.yml --debug -``` - -
+At the same time, it's recommended to create presets using your own agent — either via a CLI such as Claude Code, or inside your IDE. Your agent helps you design the preset configuration, formulate hypotheses, and — most importantly — analyze the session's traces as well as the trial results (stored under `~/.dstack/presets//trials//trial.json`), to decide what the next session can be and what instructions to give it via `prompt`. -The trace is written to `~/.dstack/presets//trace.jsonl` while the session runs. It contains the agent's messages and every tool call with its result. +> To help your agent use `dstack` and presets, install the [`dstack`](https://skills.sh/dstackai/dstack/dstack) +> and [`dstack-presets`](https://skills.sh/dstackai/dstack/dstack-presets) skills with `npx skills add dstackai/dstack`. ## Limitations diff --git a/mkdocs/docs/reference/cli/dstack/preset.md b/mkdocs/docs/reference/cli/dstack/preset.md index 3aacffe73..bff3a597d 100644 --- a/mkdocs/docs/reference/cli/dstack/preset.md +++ b/mkdocs/docs/reference/cli/dstack/preset.md @@ -49,9 +49,9 @@ Preset creation uses the existing `claude` login unless | `DSTACK_AGENT_CLAUDE_EFFORT` | Claude effort level: `low`, `medium`, `high`, `xhigh`, or `max`. If unset, the `claude` CLI default is used. | Agent progress is written to `agent.log` under `~/.dstack/presets//`, -alongside the effective configuration (`preset.dstack.yml`) and the recorded -trials (`trials.jsonl`). Pass `--debug` to also save the agent prompt -(`prompt.md`) and raw trace (`trace.jsonl`). +alongside the effective configuration (`preset.dstack.yml`), the recorded +trials, the agent prompt (`prompt.md`), and the real-time trace +(`trace.jsonl`). ## dstack preset logs diff --git a/skills/dstack-presets/SKILL.md b/skills/dstack-presets/SKILL.md new file mode 100644 index 000000000..bae536306 --- /dev/null +++ b/skills/dstack-presets/SKILL.md @@ -0,0 +1,29 @@ +--- +name: dstack-presets +description: | + Create and manage dstack presets: a toolkit that streamlines model inference optimization with agents, and a portable preset format. Use together with the dstack skill, and only when the user explicitly asks to create a preset or manage existing presets, not for deploying or serving a model. +--- + +# dstack Presets + +Use `/dstack` for CLI commands, YAML fields, apply behavior, fleets, and other +dstack syntax. This skill covers creating and managing presets. + +## Overview + +Presets offer two things: a toolkit that streamlines model inference optimization using agents, and a portable format that deploys the final preset to any cloud, Kubernetes cluster, or bare-metal fleet. A preset holds the serving configuration that produced the result, the benchmark it reached, and the exact hardware it was verified on. + +Presets are used for three kinds of work: finding an optimized baseline, optimizing through patching source code, and supporting new hardware. + +**When to use this skill:** +- The user explicitly asks to create a preset, or to optimize model inference via a preset +- Managing already created presets: watching sessions, listing, exporting, and deleting them via `dstack preset` commands + +**When NOT to use this skill:** +- Deploying or serving a model: use a service instead (see the `dstack` skill) + +## How to use presets + +Follow the [presets documentation](https://dstack.ai/docs/concepts/presets.md). + +[Configuration reference](https://dstack.ai/docs/reference/dstack.yml/preset.md) | [CLI reference](https://dstack.ai/docs/reference/cli/dstack/preset.md) diff --git a/src/dstack/_internal/cli/models/preset_agent.py b/src/dstack/_internal/cli/models/preset_agent.py index abc06e099..24629d1a4 100644 --- a/src/dstack/_internal/cli/models/preset_agent.py +++ b/src/dstack/_internal/cli/models/preset_agent.py @@ -118,7 +118,6 @@ class PresetSessionState(CoreModel): trials_num: Optional[int] previous: list[str] created_at: datetime - debug: bool status: PresetSessionStatus # None is a detached session. owner: Optional[PresetSessionProcess] diff --git a/src/dstack/_internal/cli/services/configurators/preset.py b/src/dstack/_internal/cli/services/configurators/preset.py index 59437e31c..620092b0a 100644 --- a/src/dstack/_internal/cli/services/configurators/preset.py +++ b/src/dstack/_internal/cli/services/configurators/preset.py @@ -66,7 +66,6 @@ def apply_configuration( configuration=conf, store=store, keep_service=configurator_args.keep_service, - debug=configurator_args.debug, user_prompt=user_prompt, allowed_fleets=allowed_fleets, previous=previous, @@ -131,11 +130,6 @@ def register_creation_args(parser: ArgsParser) -> None: metavar="N", help="The number of benchmarked trials before the best one is promoted", ) - parser.add_argument( - "--debug", - action="store_true", - help="Save the agent prompt and raw trace", - ) parser.add_argument( "--previous", action="append", diff --git a/src/dstack/_internal/cli/services/presets/agent.py b/src/dstack/_internal/cli/services/presets/agent.py index f9e4a1934..a8c436675 100644 --- a/src/dstack/_internal/cli/services/presets/agent.py +++ b/src/dstack/_internal/cli/services/presets/agent.py @@ -375,7 +375,7 @@ def _prepare_subprocess_command(command: list[str]) -> list[str]: return [comspec, "/d", "/s", "/c", subprocess.list2cmdline(command)] -def _write_debug_trace( +def _write_trace( session: PresetSession, *, stream_name: Literal["stdout", "stderr"], @@ -523,7 +523,7 @@ async def _read_process_stream( redacted_values: Sequence[str], session: PresetSession, ) -> PresetAgentProcessOutput: - # stderr feeds the debug trace and advances the persisted offset, but only + # stderr feeds the trace and advances the persisted offset, but only # stdout can carry the report. parse_result = stream_name == "stdout" output = PresetAgentProcessOutput() @@ -532,13 +532,12 @@ async def _read_process_stream( if not line: return output text = line.decode(errors="replace") - if session.debug: - _write_debug_trace( - session, - stream_name=stream_name, - text=text, - redacted_values=redacted_values, - ) + _write_trace( + session, + stream_name=stream_name, + text=text, + redacted_values=redacted_values, + ) if not parse_result: continue try: diff --git a/src/dstack/_internal/cli/services/presets/create.py b/src/dstack/_internal/cli/services/presets/create.py index b15c1ad15..02fbfd92b 100644 --- a/src/dstack/_internal/cli/services/presets/create.py +++ b/src/dstack/_internal/cli/services/presets/create.py @@ -400,7 +400,6 @@ def create_preset( store: PresetStore, keep_service: bool = False, build_name: Optional[str] = None, - debug: bool = False, resume_session: Optional[PresetSession] = None, user_prompt: Optional[str] = None, allowed_fleets: Optional[tuple[str, ...]] = None, @@ -409,7 +408,6 @@ def create_preset( session = resume_session or create_preset_session( configuration, previous=tuple(session.preset_id for session in previous), - debug=debug, ) try: resolved_configuration = _resolve_preset_env(configuration) @@ -629,10 +627,9 @@ async def _create_preset( # A second, persistent copy: the workspace above is deleted with the run, # while the listing and `--previous` read constraints from the session dir. session.write_constraints(constraints_text) - if session.debug: - session.write_prompt(prompt) - if setup.auth is not None: - session.write_agent_info(setup.auth) + session.write_prompt(prompt) + if setup.auth is not None: + session.write_agent_info(setup.auth) try: if mode == "attach": process_output = await attach_preset_agent( @@ -685,12 +682,11 @@ async def _create_preset( interrupted = True raise finally: - if session.debug: - _save_final_report_copy( - workspace=setup.workspace, - session=session, - redacted_values=redacted_values, - ) + _save_final_report_copy( + workspace=setup.workspace, + session=session, + redacted_values=redacted_values, + ) if not interrupted: keep_final_service = keep_service and creation_succeeded try: @@ -1009,8 +1005,7 @@ async def _cleanup_runs( pending.remove(name) if pending: await asyncio.sleep(2) - if session.debug: - print_preset_progress("All preset creation runs stopped.", session=session) + print_preset_progress("All preset creation runs stopped.", session=session) def _load_submitted_run_names(path: Path) -> list[str]: diff --git a/src/dstack/_internal/cli/services/presets/session.py b/src/dstack/_internal/cli/services/presets/session.py index fb9ace9f8..6c0588919 100644 --- a/src/dstack/_internal/cli/services/presets/session.py +++ b/src/dstack/_internal/cli/services/presets/session.py @@ -56,7 +56,6 @@ class SessionBusyError(CLIError): @dataclass class PresetSession: path: Path - debug: bool preset_id: str # Background reconcile sets this False so finalizing a detached session stays # silent on the read command; agent.log is written regardless. @@ -269,7 +268,6 @@ def create_preset_session( configuration: PresetConfiguration, *, previous: Sequence[str], - debug: bool, ) -> PresetSession: if configuration.name is None: raise CLIError("The service name is required to save agent output") @@ -286,7 +284,8 @@ def create_preset_session( continue break _write_private_text(path / "agent.log", "") - session = PresetSession(path=path, debug=debug, preset_id=preset_id) + _write_private_text(path / "trace.jsonl", "") + session = PresetSession(path=path, preset_id=preset_id) session.write_state( PresetSessionState( id=preset_id, @@ -295,7 +294,6 @@ def create_preset_session( trials_num=configuration.trials, previous=list(previous), created_at=datetime.now(timezone.utc), - debug=debug, status="running", owner=_current_process(), run=None, @@ -311,8 +309,6 @@ def create_preset_session( path / "preset.dstack.yml", yaml.safe_dump(record, sort_keys=False), ) - if debug: - _write_private_text(path / "trace.jsonl", "") except OSError as e: if path is not None: shutil.rmtree(path, ignore_errors=True) @@ -322,7 +318,7 @@ def create_preset_session( def load_resumable_session(preset_id: str) -> PresetSession: path = get_presets_dir() / preset_id - session = PresetSession(path=path, debug=False, preset_id=preset_id) + session = PresetSession(path=path, preset_id=preset_id) state = session.read_state() if not path.is_dir() or state is None: raise CLIError(f"Unknown preset: {preset_id}") @@ -337,7 +333,6 @@ def load_resumable_session(preset_id: str) -> PresetSession: ) if state.run is None or state.run.claude_session_id is None: raise CLIError(f"Preset {preset_id} creation stopped before it started; create a new one") - session.debug = state.debug return session @@ -368,7 +363,7 @@ def session_process_alive(state: PresetSessionState) -> bool: def load_attachable_session(preset_id: str) -> PresetSession: path = get_presets_dir() / preset_id - session = PresetSession(path=path, debug=False, preset_id=preset_id) + session = PresetSession(path=path, preset_id=preset_id) state = session.read_state() if not path.is_dir() or state is None: raise CLIError(f"Unknown preset: {preset_id}") @@ -387,13 +382,12 @@ def load_attachable_session(preset_id: str) -> PresetSession: f"Preset {preset_id} is already being followed by another CLI (pid {owner.pid});" f" stop or detach it there with Ctrl+C" ) - session.debug = state.debug return session def load_preset_session(preset_id: str) -> PresetSession: path = get_presets_dir() / preset_id - session = PresetSession(path=path, debug=False, preset_id=preset_id) + session = PresetSession(path=path, preset_id=preset_id) if not path.is_dir() or session.read_state() is None: raise CLIError(f"Unknown preset: {preset_id}") return session @@ -476,7 +470,7 @@ def iter_preset_sessions() -> Iterator[PresetSession]: return for path in sorted(root.iterdir()): if path.is_dir() and not path.name.startswith((".", "models--")): - yield PresetSession(path=path, debug=False, preset_id=path.name) + yield PresetSession(path=path, preset_id=path.name) def find_session_name_claims(name: str) -> list[PresetSession]: diff --git a/src/tests/_internal/cli/commands/test_preset.py b/src/tests/_internal/cli/commands/test_preset.py index 26cda776b..326a56741 100644 --- a/src/tests/_internal/cli/commands/test_preset.py +++ b/src/tests/_internal/cli/commands/test_preset.py @@ -485,7 +485,6 @@ def test_merges_profile_configuration_and_cli_args(self, tmp_path): "0.75", "--fleet", "cli-fleet", - "--debug", ], home_dir=tmp_path, repo_dir=tmp_path, @@ -499,7 +498,6 @@ def test_merges_profile_configuration_and_cli_args(self, tmp_path): assert configuration.max_price == 0.75 assert configuration.spot_policy.value == "spot" assert [fleet.format() for fleet in configuration.fleets] == ["cli-fleet"] - assert create.call_args.kwargs["debug"] is True def test_create_detaches_the_name_from_the_old_preset(self, tmp_path): preset = get_preset().model_copy(update={"name": "qwen"}) @@ -630,7 +628,6 @@ def test_accepts_creation_and_profile_arguments(self, tmp_path): "7", "--backend", "gcp", - "--debug", ], home_dir=tmp_path, repo_dir=tmp_path, @@ -641,7 +638,6 @@ def test_accepts_creation_and_profile_arguments(self, tmp_path): assert configuration.name == "cli-name" assert configuration.trials == 7 assert configuration.backends == ["gcp"] - assert create.call_args.kwargs["debug"] is True def test_rejects_detach(self, tmp_path, capsys): configuration_path = self._write_configuration(tmp_path) diff --git a/src/tests/_internal/cli/common.py b/src/tests/_internal/cli/common.py index 081eb8018..182c8d6a2 100644 --- a/src/tests/_internal/cli/common.py +++ b/src/tests/_internal/cli/common.py @@ -210,7 +210,6 @@ def get_session_state(**overrides: Any) -> PresetSessionState: "trials_num": None, "previous": [], "created_at": datetime(2026, 1, 2, 3, 4, tzinfo=timezone.utc), - "debug": False, "status": "running", "owner": None, "run": None, diff --git a/src/tests/_internal/cli/services/presets/test_agent.py b/src/tests/_internal/cli/services/presets/test_agent.py index 742a85373..9182b92b4 100644 --- a/src/tests/_internal/cli/services/presets/test_agent.py +++ b/src/tests/_internal/cli/services/presets/test_agent.py @@ -177,7 +177,7 @@ def test_detects_known_secret_in_generated_artifact(self): def _session_workspace(tmp_path): session_dir = tmp_path / "session-under-test" session_dir.mkdir() - session = PresetSession(path=session_dir, debug=False, preset_id="abcd1234") + session = PresetSession(path=session_dir, preset_id="abcd1234") session.write_state(get_session_state(id="abcd1234")) workspace, _ = create_agent_workspace(session) return workspace @@ -199,7 +199,7 @@ def _home(self, tmp_path, monkeypatch) -> None: def test_creates_private_session_with_log_and_manifest(self, tmp_path, monkeypatch, capsys): self._home(tmp_path, monkeypatch) - session = create_preset_session(self._configuration(), previous=(), debug=False) + session = create_preset_session(self._configuration(), previous=()) assert session.path.parent == tmp_path / ".dstack" / "presets" assert session.path.name == session.preset_id @@ -208,6 +208,7 @@ def test_creates_private_session_with_log_and_manifest(self, tmp_path, monkeypat "agent.log", "session.json", "preset.dstack.yml", + "trace.jsonl", } state = json.loads((session.path / "session.json").read_text()) assert state["id"] == session.preset_id @@ -222,26 +223,20 @@ def test_creates_private_session_with_log_and_manifest(self, tmp_path, monkeypat assert session.path.stat().st_mode & 0o777 == 0o700 assert session.log_path.stat().st_mode & 0o777 == 0o600 - def test_debug_session_saves_scrubbed_configuration_and_trace(self, tmp_path, monkeypatch): + def test_session_saves_scrubbed_configuration(self, tmp_path, monkeypatch): self._home(tmp_path, monkeypatch) - debug_session = create_preset_session(self._configuration(), previous=(), debug=True) + session = create_preset_session(self._configuration(), previous=()) - data = yaml.safe_load((debug_session.path / "preset.dstack.yml").read_text()) - assert {path.name for path in debug_session.path.iterdir()} == { - "agent.log", - "preset.dstack.yml", - "session.json", - "trace.jsonl", - } + data = yaml.safe_load((session.path / "preset.dstack.yml").read_text()) assert data["max_price"] == 0.5 assert data["env"] == ["HF_TOKEN", "TOKENIZERS_PARALLELISM"] - assert "false" not in (debug_session.path / "preset.dstack.yml").read_text() + assert "false" not in (session.path / "preset.dstack.yml").read_text() @pytest.mark.parametrize("status", ["success", "failed"]) def test_finish_records_terminal_status(self, tmp_path, monkeypatch, status): self._home(tmp_path, monkeypatch) - session = create_preset_session(self._configuration(), previous=(), debug=False) + session = create_preset_session(self._configuration(), previous=()) finished_path = session.finish(status) @@ -254,7 +249,6 @@ def test_finish_writes_status_in_place(self, tmp_path): (session_dir / "agent.log").touch() session = PresetSession( path=session_dir, - debug=False, preset_id="ab12cd34", ) session.write_state(get_session_state()) @@ -274,7 +268,7 @@ def test_reports_invalid_existing_parent(self, tmp_path, monkeypatch): with pytest.raises(CLIError, match="Could not create agent output"): create_preset_session( - PresetConfiguration(name="qwen", base="Qwen/Qwen3.5-27B"), previous=(), debug=False + PresetConfiguration(name="qwen", base="Qwen/Qwen3.5-27B"), previous=() ) def test_log_write_failure_warns_once(self, tmp_path, capsys): @@ -283,7 +277,6 @@ def test_log_write_failure_warns_once(self, tmp_path, capsys): (path / "agent.log").touch() session = PresetSession( path=path, - debug=False, preset_id="ab12cd34", ) shutil.rmtree(path) @@ -326,13 +319,12 @@ async def test_sends_prompt_and_redacts_raw_output(self, tmp_path, monkeypatch, ) workspace = PresetAgentWorkspace(path=tmp_path, dstack_home=tmp_path / "home") - session_path = tmp_path / "debug-running" + session_path = tmp_path / "session-running" session_path.mkdir() (session_path / "agent.log").touch() (session_path / "trace.jsonl").touch() session = PresetSession( path=session_path, - debug=True, preset_id="ab12cd34", ) output = await run_preset_agent( @@ -385,7 +377,7 @@ async def test_mirrors_trial_and_service_records_into_the_session(self, tmp_path session_path = tmp_path / "session" session_path.mkdir() (session_path / "agent.log").touch() - session = PresetSession(path=session_path, debug=False, preset_id="ab12cd34") + session = PresetSession(path=session_path, preset_id="ab12cd34") output = await run_preset_agent( prompt="p", @@ -434,7 +426,6 @@ async def test_accepts_stream_event_larger_than_64_kib(self, tmp_path, monkeypat redacted_values=(), session=PresetSession( path=session_path, - debug=False, preset_id="ab12cd34", ), ) @@ -450,7 +441,6 @@ def test_progress_stream_prints_only_redacted_messages(self, tmp_path, capsys): (session_path / "agent.log").touch() session = PresetSession( path=session_path, - debug=False, preset_id="ab12cd34", ) @@ -637,7 +627,7 @@ def test_writes_model_params_and_auth(self, tmp_path, monkeypatch): ) session_dir = tmp_path / "session" session_dir.mkdir() - session = PresetSession(path=session_dir, debug=True, preset_id="ab12cd34") + session = PresetSession(path=session_dir, preset_id="ab12cd34") session.write_agent_info( ClaudeAuth(api_key=None, executable="claude", effort=None, model="claude-opus-4-8") @@ -654,7 +644,7 @@ def test_writes_model_params_and_auth(self, tmp_path, monkeypatch): def _offsets(tmp_path): session_dir = tmp_path / "offsets-session" session_dir.mkdir(exist_ok=True) - return open_session_offsets(PresetSession(path=session_dir, debug=False, preset_id="offsets0")) + return open_session_offsets(PresetSession(path=session_dir, preset_id="offsets0")) def _subprocess_env() -> dict[str, str]: @@ -679,7 +669,6 @@ def _agent_setup(tmp_path): (session_path / "agent.log").touch() session = PresetSession( path=session_path, - debug=False, preset_id="ab12cd34", ) session.write_state(get_session_state()) @@ -870,7 +859,7 @@ class TestWorkspaceLifecycle: def _session(self, tmp_path): session_dir = tmp_path / "sessions" / "ab12cd34" session_dir.mkdir(parents=True) - session = PresetSession(path=session_dir, debug=False, preset_id="ab12cd34") + session = PresetSession(path=session_dir, preset_id="ab12cd34") session.write_state(get_session_state()) return session @@ -976,7 +965,7 @@ def test_reads_a_pre_0_22_flat_session_file(self, tmp_path): session_dir = tmp_path / "30a012bf" session_dir.mkdir() (session_dir / "session.json").write_text(json.dumps(flat)) - session = PresetSession(path=session_dir, debug=False, preset_id="30a012bf") + session = PresetSession(path=session_dir, preset_id="30a012bf") state = session.read_state() @@ -1012,7 +1001,6 @@ def test_loads_interrupted_session(self, tmp_path, monkeypatch): "id": "ab12cd34", "status": "interrupted", "run": get_session_run(claude_session_id="sid-1"), - "debug": True, "created_at": "2026-07-20T10:00:00Z", }, ) @@ -1020,7 +1008,6 @@ def test_loads_interrupted_session(self, tmp_path, monkeypatch): session = load_resumable_session("ab12cd34") assert session.preset_id == "ab12cd34" - assert session.debug is True def test_treats_dead_running_session_as_resumable(self, tmp_path, monkeypatch): self._write_session( @@ -1246,7 +1233,7 @@ def test_detach_keeps_the_agent_and_stop_terminates_it(self, tmp_path, monkeypat session_dir = tmp_path / "ab12cd34" session_dir.mkdir() (session_dir / "agent.log").touch() - session = PresetSession(path=session_dir, debug=False, preset_id="ab12cd34") + session = PresetSession(path=session_dir, preset_id="ab12cd34") session.write_state(get_session_state()) agent = subprocess.Popen( [sys.executable, "-c", "import time; time.sleep(300)"], start_new_session=True diff --git a/src/tests/_internal/cli/services/presets/test_create.py b/src/tests/_internal/cli/services/presets/test_create.py index aa21760f6..683fb55a9 100644 --- a/src/tests/_internal/cli/services/presets/test_create.py +++ b/src/tests/_internal/cli/services/presets/test_create.py @@ -133,7 +133,7 @@ def creation_context(tmp_path, monkeypatch): class TestCreatePreset: - def test_saves_agent_log_without_debug(self, tmp_path, monkeypatch): + def test_saves_agent_log_and_trace(self, tmp_path, monkeypatch): monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("USERPROFILE", str(tmp_path)) preset = get_preset() @@ -167,13 +167,14 @@ async def create(**kwargs): "agent.log", "session.json", "preset.dstack.yml", + "trace.jsonl", } state = json.loads((paths[0] / "session.json").read_text()) assert state["status"] == "success" assert state["id"] == paths[0].name assert "testing preset" in (paths[0] / "agent.log").read_text() - def test_debug_finalization_error_does_not_mask_success(self, tmp_path, monkeypatch, capsys): + def test_finalization_error_does_not_mask_success(self, tmp_path, monkeypatch, capsys): monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("USERPROFILE", str(tmp_path)) monkeypatch.setenv("HF_TOKEN", "hf-secret") @@ -211,7 +212,6 @@ def fail_finish(self, preset_id=None): env=["HF_TOKEN", "TOKENIZERS_PARALLELISM=false"], ), store=PresetStore(tmp_path / "presets"), - debug=True, ) paths = _session_dirs(tmp_path) @@ -228,7 +228,7 @@ def fail_finish(self, preset_id=None): assert "hf-secret" not in (paths[0] / "preset.dstack.yml").read_text() assert "Files remain at" in capsys.readouterr().out - def test_debug_finalization_does_not_mask_creation_error(self, tmp_path, monkeypatch): + def test_finalization_does_not_mask_creation_error(self, tmp_path, monkeypatch): monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("USERPROFILE", str(tmp_path)) @@ -252,7 +252,6 @@ def fail_finish(self, preset_id=None): base="Qwen/Qwen3.5-27B", ), store=PresetStore(tmp_path / "presets"), - debug=True, ) @pytest.mark.asyncio @@ -351,13 +350,12 @@ async def run_agent(**kwargs): async def test_saves_preset_and_cleans_up_runs( self, creation_context, monkeypatch, keep_service, stopped_names, tmp_path ): - session_path = tmp_path / "debug-running" + session_path = tmp_path / "session-running" session_path.mkdir() (session_path / "agent.log").touch() (session_path / "trace.jsonl").touch() session = PresetSession( path=session_path, - debug=True, preset_id="ab12cd34", ) session.write_state(get_session_state()) @@ -517,7 +515,7 @@ async def test_installs_records_pins_manifest_and_extends_the_prompt( ) session_path = tmp_path / "fresh" session_path.mkdir() - session = PresetSession(path=session_path, debug=False, preset_id="ab12cd34") + session = PresetSession(path=session_path, preset_id="ab12cd34") session.write_state(get_session_state(previous=["8d3b01aa"])) seen = {} @@ -550,7 +548,7 @@ async def run_agent(**kwargs): assert "## Previous Sessions" in seen["prompt"] assert "8d3b01aa" in seen["prompt"] assert session.read_state().previous == ["8d3b01aa"] - # constraints.json is a session record even without --debug. + # constraints.json is a persistent session record. assert (session_path / "constraints.json").is_file() @@ -598,15 +596,13 @@ async def no_sleep(_): assert runs.stopped_names == ["qwen-build-1"] -def _agent_session(tmp_path, *, debug: bool = False) -> PresetSession: +def _agent_session(tmp_path) -> PresetSession: path = tmp_path / "agent-running" path.mkdir() (path / "agent.log").touch() - if debug: - (path / "trace.jsonl").touch() + (path / "trace.jsonl").touch() session = PresetSession( path=path, - debug=debug, preset_id="ab12cd34", ) session.write_state(get_session_state()) @@ -836,7 +832,7 @@ def test_copies_report_redacted(self, tmp_path): workspace.final_report_path.write_text( '{"success": true, "note": "token dstack-secret"}', encoding="utf-8" ) - session = _agent_session(tmp_path, debug=True) + session = _agent_session(tmp_path) _save_final_report_copy( workspace=workspace, @@ -851,7 +847,7 @@ def test_copies_report_redacted(self, tmp_path): def test_missing_report_is_no_op(self, tmp_path): workspace = PresetAgentWorkspace(path=tmp_path / "w", dstack_home=tmp_path / "h") workspace.path.mkdir() - session = _agent_session(tmp_path, debug=True) + session = _agent_session(tmp_path) _save_final_report_copy( workspace=workspace, @@ -894,7 +890,7 @@ def test_suspend_scrubs_workspace_token(self, tmp_path, capsys): session_dir = tmp_path / "ab12cd34" session_dir.mkdir() (session_dir / "agent.log").touch() - session = PresetSession(path=session_dir, debug=False, preset_id="ab12cd34") + session = PresetSession(path=session_dir, preset_id="ab12cd34") session.write_state(get_session_state()) workspace_root = tmp_path / "workspace" config_dir = workspace_root / "h" / ".dstack" @@ -921,7 +917,7 @@ async def test_resume_uses_saved_claude_session(self, creation_context, monkeypa session_dir = tmp_path / "sessions" / "fe98dc76" session_dir.mkdir(parents=True) (session_dir / "agent.log").touch() - session = PresetSession(path=session_dir, debug=False, preset_id="fe98dc76") + session = PresetSession(path=session_dir, preset_id="fe98dc76") session.write_state(get_session_state(id="fe98dc76")) workspace, workspace_record = create_agent_workspace(session) workspace.constraints_path.write_text( @@ -970,7 +966,7 @@ async def test_pins_user_prompt_on_create(self, creation_context, monkeypatch, t session_dir = tmp_path / "ab34ef12" session_dir.mkdir() (session_dir / "agent.log").touch() - session = PresetSession(path=session_dir, debug=False, preset_id="ab34ef12") + session = PresetSession(path=session_dir, preset_id="ab34ef12") session.write_state(get_session_state(id="ab34ef12")) captured = {} @@ -1008,7 +1004,7 @@ async def test_resume_keeps_the_pinned_user_prompt( session_dir = tmp_path / "ab34ef12" session_dir.mkdir() (session_dir / "agent.log").touch() - session = PresetSession(path=session_dir, debug=False, preset_id="ab34ef12") + session = PresetSession(path=session_dir, preset_id="ab34ef12") session.write_state(get_session_state(id="ab34ef12")) workspace, workspace_record = create_agent_workspace(session) workspace.constraints_path.write_text( @@ -1075,7 +1071,7 @@ def _session(self, tmp_path, preset_id: str, status: str, log: str) -> PresetSes session_dir = tmp_path / preset_id session_dir.mkdir() (session_dir / "agent.log").write_text(log) - session = PresetSession(path=session_dir, debug=False, preset_id=preset_id) + session = PresetSession(path=session_dir, preset_id=preset_id) session.write_state(get_session_state(id=preset_id, status=status)) return session @@ -1126,7 +1122,7 @@ def _detached_session(self, tmp_path, configuration_yaml: str) -> PresetSession: session_dir.mkdir() (session_dir / "agent.log").touch() (session_dir / "preset.dstack.yml").write_text(configuration_yaml) - session = PresetSession(path=session_dir, debug=False, preset_id="ab12cd34") + session = PresetSession(path=session_dir, preset_id="ab12cd34") workspace, workspace_record = create_agent_workspace(session) workspace.constraints_path.write_text('{"run_name_prefix": "qwen-build"}') session.write_state( @@ -1336,7 +1332,7 @@ def _session(self, tmp_path) -> PresetSession: (session_dir / "runs.jsonl").write_text( '{"name":"qwen-build-1","id":"a"}\n{"name":"qwen-build-2","id":"b"}\n' ) - return PresetSession(path=session_dir, debug=False, preset_id="ab12cd34") + return PresetSession(path=session_dir, preset_id="ab12cd34") def _api(self, statuses: dict, stopped: list) -> SimpleNamespace: def get(project, name): @@ -1484,7 +1480,7 @@ def boom(**kwargs): class TestSessionClaim: def _session(self, tmp_path): (tmp_path / "sess").mkdir() - return PresetSession(path=tmp_path / "sess", debug=False, preset_id="sess") + return PresetSession(path=tmp_path / "sess", preset_id="sess") def test_claim_is_exclusive_and_releasable(self, tmp_path): session = self._session(tmp_path) @@ -1533,7 +1529,7 @@ def test_dead_pids_are_not_alive(self): class TestBeginRun: def test_records_the_run_whole_and_keeps_claude_state(self, tmp_path): (tmp_path / "s").mkdir() - session = PresetSession(path=tmp_path / "s", debug=False, preset_id="s") + session = PresetSession(path=tmp_path / "s", preset_id="s") workspace = PresetSessionWorkspace(path=str(tmp_path / "w"), alias=str(tmp_path / "w")) session.write_state( get_session_state( diff --git a/src/tests/_internal/cli/services/presets/test_workspace.py b/src/tests/_internal/cli/services/presets/test_workspace.py index 48a7c1aba..ee193fada 100644 --- a/src/tests/_internal/cli/services/presets/test_workspace.py +++ b/src/tests/_internal/cli/services/presets/test_workspace.py @@ -27,7 +27,7 @@ def _previous_session(tmp_path, preset_id="8d3b01aa"): (root / "runs.jsonl").write_text("{}") (root / "trials" / "not-a-trial").mkdir() (root / "trials" / "not-a-trial" / "trial.json").write_text("{}") - return PresetSession(path=root, debug=False, preset_id=preset_id) + return PresetSession(path=root, preset_id=preset_id) def _workspace(tmp_path): @@ -74,7 +74,7 @@ def test_a_session_without_records_warns(self, tmp_path, capsys): root = tmp_path / "store" / "empty000" root.mkdir(parents=True) (root / "session.json").write_text("{}") - session = PresetSession(path=root, debug=False, preset_id="empty000") + session = PresetSession(path=root, preset_id="empty000") workspace = _workspace(tmp_path) install_previous_records(workspace, [session])