From c90a95ed79249266bdc669efc90810cbb309ad0d Mon Sep 17 00:00:00 2001 From: "Aliaksei Yaletski (Tiendil)" Date: Wed, 1 Apr 2026 14:56:11 +0200 Subject: [PATCH 1/2] wip --- README.md | 10 +---- donna/artifacts/usage/artifacts.md | 6 +-- donna/artifacts/usage/cli.md | 4 +- donna/cli/commands/artifacts.py | 59 +-------------------------- donna/cli/errors.py | 9 ---- donna/cli/types.py | 10 ----- donna/context/artifacts.py | 14 ------- donna/machine/sessions.py | 3 -- donna/workspaces/artifacts.py | 23 ----------- donna/workspaces/config.py | 1 - donna/workspaces/tmp.py | 51 ----------------------- donna/workspaces/worlds/base.py | 3 -- donna/workspaces/worlds/filesystem.py | 8 ---- donna/workspaces/worlds/python.py | 8 ---- 14 files changed, 5 insertions(+), 204 deletions(-) delete mode 100644 donna/workspaces/tmp.py diff --git a/README.md b/README.md index dbc3921..5cb4da5 100644 --- a/README.md +++ b/README.md @@ -228,17 +228,9 @@ By default, Donna uses the next worlds: - `project` — project-level artifacts in `/.donna/project/` folder; - `session` — session-level artifacts in `/.donna/session/` folder. -Besides that, there is `/.donna/tmp` folder used to store temporary files. - A world can be read-only. By default, writable worlds are `session` (current work scope) and `project` (project scope). -Agents are not allowed to edit artifacts directly because artifact consistency is important. Instead, they follow the next algorithm: - -- Fetch an artifact into the temporary file with the command `donna -p llm artifacts fetch ...`. -- Edit the temporary file. -- Upload an artifact with the command `donna -p llm artifacts upload ...`. - -On upload, Donna validates the artifact and accepts it only when there are no errors. For example, Donna will not accept a workflow that can not be finished. +Agents are not allowed to edit artifacts through Donna CLI directly because artifact consistency is important. Instead, they update the underlying files in writable worlds and rely on Donna validation when needed. ### Rendering diff --git a/donna/artifacts/usage/artifacts.md b/donna/artifacts/usage/artifacts.md index 1c82f41..b87059b 100644 --- a/donna/artifacts/usage/artifacts.md +++ b/donna/artifacts/usage/artifacts.md @@ -18,8 +18,6 @@ The text artifact has a source and one or more rendered representations, produce To change the artifact, developers and agents edit its source. -When you need a scratch file for artifact-related work, use `donna -p artifacts tmp '.'` to create a temporary file in the workspace temp directory. - To get information from the artifact, developers, agents and Donna view one of its representations (typically via the view rendering mode). **If you need an information from the artifact, you MUST view its representation**. Artifact sources are only for editing. @@ -46,7 +44,7 @@ Here are some examples: Donna allows all of Jinja2 expressions in artifact sources, except inheritance-related once: `{{ "{% extends %}" }}` , `{{ "{% block %}" }}`, etc. -Donna intentionally hides some parts of the source in the rendered output, but they remain visible in source views (for example, in `artifacts fetch` output or on GitHub): +Donna intentionally hides some parts of the source in the rendered output, but they remain visible in the source files themselves (on filesystem): - fenced code blocks with the `donna` marker (they contain technical information for the Donna, not information for the agent). - Jinja2 comments like `{{ "{# ... #}" }}`. @@ -100,7 +98,7 @@ The configuration block properties format is `property1 property2=value2 propert The content of the block is parsed according to the primary format and interpreted according its properties. -Configuration blocks are parsed by Donna and removed from rendered Markdown representations (see "Jinja2 rendering"); they remain in the source for editing and inspection (e.g., via `artifacts fetch` or the repository file). +Configuration blocks are parsed by Donna and removed from rendered Markdown representations (see "Jinja2 rendering"); they remain in the source for editing and inspection on the file system. Fences without `donna` keyword are considered regular code blocks and have no special meaning for Donna. diff --git a/donna/artifacts/usage/cli.md b/donna/artifacts/usage/cli.md index c4a1832..bf21ad4 100644 --- a/donna/artifacts/usage/cli.md +++ b/donna/artifacts/usage/cli.md @@ -75,7 +75,7 @@ There are four sets of commands: - `donna -p workspaces …` — manages workspaces. Most-likely it will be used once per your project to initialize it. - `donna -p sessions …` — manages sessions. You will use these commands to start, push forward, and manage your work. -- `donna -p artifacts …` — manages artifact discovery, reading, fetching, temporary files, and validation. +- `donna -p artifacts …` — manages artifact discovery, reading, and validation. - `donna -p journal …` — manages session actions journal. You will use these commands to log and inspect the history of actions performed during the session. Use: @@ -144,8 +144,6 @@ Use the next commands to work with artifacts: - `donna -p artifacts list []` — list all artifacts corresponding to the given pattern. If `` is omitted, list all artifacts in all worlds. Use this command when you need to find an artifact or see what artifacts are available. - `donna -p artifacts view ` — get the meaningful (rendered) content of all matching artifacts. This command shows the rendered information about each artifact. Use this command when you need to read artifact content. -- `donna -p artifacts fetch :` — download the original source of the artifact content, outputs the file path to the artifact's copy, you can change. Use this command when you need to change the content of the artifact. -- `donna -p artifacts tmp .` — create a temporary file for artifact-related work and output its path. - `donna -p artifacts validate []` — validate all artifacts corresponding to the given pattern. If `` is omitted, validate all artifacts in all worlds. Donna does not mutate artifacts stored in worlds. Developers and external tools are responsible for creating, updating, moving, copying, or deleting world artifacts before Donna reads or validates them. diff --git a/donna/cli/commands/artifacts.py b/donna/cli/commands/artifacts.py index f374648..d74abb7 100644 --- a/donna/cli/commands/artifacts.py +++ b/donna/cli/commands/artifacts.py @@ -2,23 +2,17 @@ import typer -from donna.cli import errors as cli_errors from donna.cli.application import app from donna.cli.types import ( - FullArtifactIdArgument, FullArtifactIdPatternArgument, - OutputPathOption, PredicateOption, ) from donna.cli.utils import cells_cli from donna.context.context import context -from donna.core.errors import ErrorsList -from donna.core.result import Err, Ok, Result from donna.domain.ids import FullArtifactIdPattern from donna.machine import journal as machine_journal from donna.protocol.cell_shortcuts import operation_succeeded from donna.protocol.cells import Cell -from donna.workspaces import tmp as world_tmp from donna.workspaces.artifacts import RENDER_CONTEXT_VIEW artifacts_cli = typer.Typer() @@ -26,18 +20,6 @@ DEFAULT_ARTIFACT_PATTERN = FullArtifactIdPattern.parse("**").unwrap() -def _parse_slug_with_extension(value: str) -> Result[tuple[str, str], ErrorsList]: - normalized = value.strip() - if "." not in normalized: - return Err([cli_errors.InvalidSlugWithExtension(value=normalized)]) - - slug, extension = normalized.rsplit(".", 1) - if not slug or not extension: - return Err([cli_errors.InvalidSlugWithExtension(value=normalized)]) - - return Ok((slug, extension)) - - def _log_artifact_operation(message: str) -> None: machine_journal.add(message=message) @@ -80,45 +62,6 @@ def view( return [artifact.node().info() for artifact in artifacts] -@artifacts_cli.command( - help=( - "Fetch an artifact source into a local file. When --output is omitted, " - "a temporary file will be created in the project's temp directory." - ) -) -@cells_cli -def fetch(id: FullArtifactIdArgument, output: OutputPathOption = None) -> Iterable[Cell]: - if output is None: - extension = context().artifacts.file_extension(id).unwrap() - output = world_tmp.file_for_artifact(id, extension) - - _log_artifact_operation(f"Fetch artifact `{id}` to '{output}'") - - context().artifacts.fetch(id, output).unwrap() - - return [ - operation_succeeded(f"Artifact `{id}` fetched to '{output}'", artifact_id=str(id), output_path=str(output)) - ] - - -@artifacts_cli.command(help="Create a temporary file for artifact-related work and print its path.") -@cells_cli -def tmp( - slug_with_extension: str = typer.Argument(..., help="Temporary file slug with extension (example: 'draft.md').") -) -> Iterable[Cell]: - slug, extension = _parse_slug_with_extension(slug_with_extension).unwrap() - output = world_tmp.create_file_for_slug(slug, extension) - - _log_artifact_operation(f"Created temporary file {output}") - - return [ - operation_succeeded( - f"Temporary file created at '{output}'", - output_path=str(output), - ) - ] - - @artifacts_cli.command(help="Validate artifacts matching a pattern (defaults to all artifacts) and return any errors.") @cells_cli def validate( @@ -145,5 +88,5 @@ def validate( app.add_typer( artifacts_cli, name="artifacts", - help="Inspect, fetch, and validate stored artifacts across all Donna worlds.", + help="Inspect and validate stored artifacts across all Donna worlds.", ) diff --git a/donna/cli/errors.py b/donna/cli/errors.py index 980a15b..b05f0fa 100644 --- a/donna/cli/errors.py +++ b/donna/cli/errors.py @@ -7,12 +7,3 @@ class InternalError(core_errors.InternalError): class CliError(core_errors.EnvironmentError): cell_kind: str = "cli_error" - - -class InvalidSlugWithExtension(CliError): - code: str = "donna.cli.invalid_slug_with_extension" - message: str = "Invalid slug with extension: '{error.value}'." - ways_to_fix: list[str] = [ - "Use the format '.' (for example: 'draft.md').", - ] - value: str diff --git a/donna/cli/types.py b/donna/cli/types.py index 49171fa..a330a23 100644 --- a/donna/cli/types.py +++ b/donna/cli/types.py @@ -178,16 +178,6 @@ def _parse_input_path(value: str) -> pathlib.Path: ] -OutputPathOption = Annotated[ - pathlib.Path | None, - typer.Option( - resolve_path=True, - dir_okay=False, - file_okay=True, - help="Optional output file path (file only). Defaults to a temporary file if omitted.", - ), -] - ProjectDirArgument = Annotated[ pathlib.Path | None, typer.Argument( diff --git a/donna/context/artifacts.py b/donna/context/artifacts.py index 359a1df..2e09a0b 100644 --- a/donna/context/artifacts.py +++ b/donna/context/artifacts.py @@ -1,4 +1,3 @@ -from pathlib import Path from typing import TYPE_CHECKING from donna.context.entity_cache import TimedCache, TimedCacheValue @@ -115,19 +114,6 @@ def resolve_section( artifact = self.load(target_id.full_artifact_id, render_context).unwrap() return Ok(artifact.get_section(target_id.local_id).unwrap()) - @unwrap_to_error - def file_extension(self, full_id: FullArtifactId) -> Result[str, ErrorsList]: - from donna.workspaces import artifacts as workspace_artifacts - - return Ok(workspace_artifacts.artifact_file_extension(full_id).unwrap()) - - @unwrap_to_error - def fetch(self, full_id: FullArtifactId, output: Path) -> Result[None, ErrorsList]: - from donna.workspaces import artifacts as workspace_artifacts - - workspace_artifacts.fetch_artifact(full_id, output).unwrap() - return Ok(None) - @unwrap_to_error def _list_artifact_if_matches( self, diff --git a/donna/machine/sessions.py b/donna/machine/sessions.py index 3dcc812..0252f7f 100644 --- a/donna/machine/sessions.py +++ b/donna/machine/sessions.py @@ -11,7 +11,6 @@ from donna.machine.state import ConsistentState, MutableState from donna.protocol.cell_shortcuts import operation_succeeded from donna.protocol.cells import Cell -from donna.workspaces import tmp as world_tmp from donna.workspaces import utils as workspace_utils from donna.workspaces.artifacts import RENDER_CONTEXT_VIEW @@ -61,7 +60,6 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> CellsResult: @unwrap_to_error def start() -> Result[list[Cell], ErrorsList]: - world_tmp.clear() workspace_utils.session_world().unwrap().initialize(reset=True).unwrap() machine_journal.reset().unwrap() @@ -80,7 +78,6 @@ def reset() -> Result[list[Cell], ErrorsList]: @unwrap_to_error def clear() -> Result[list[Cell], ErrorsList]: - world_tmp.clear() workspace_utils.session_world().unwrap().initialize(reset=True).unwrap() return Ok([operation_succeeded("Cleared session.")]) diff --git a/donna/workspaces/artifacts.py b/donna/workspaces/artifacts.py index cd58d78..0eb5f26 100644 --- a/donna/workspaces/artifacts.py +++ b/donna/workspaces/artifacts.py @@ -1,11 +1,5 @@ -import pathlib - from donna.core.entities import BaseEntity -from donna.core.errors import ErrorsList -from donna.core.result import Ok, Result, unwrap_to_error -from donna.domain.ids import FullArtifactId from donna.machine.tasks import Task, WorkUnit -from donna.workspaces.config import config from donna.workspaces.templates import RenderMode @@ -16,20 +10,3 @@ class ArtifactRenderContext(BaseEntity): RENDER_CONTEXT_VIEW = ArtifactRenderContext(primary_mode=RenderMode.view) - - -@unwrap_to_error -def artifact_file_extension(full_id: FullArtifactId) -> Result[str, ErrorsList]: - world = config().get_world(full_id.world_id).unwrap() - return Ok(world.file_extension_for(full_id.artifact_id).unwrap().lstrip(".")) - - -@unwrap_to_error -def fetch_artifact(full_id: FullArtifactId, output: pathlib.Path) -> Result[None, ErrorsList]: - world = config().get_world(full_id.world_id).unwrap() - raw_artifact = world.fetch(full_id.artifact_id).unwrap() - - with output.open("wb") as f: - f.write(raw_artifact.get_bytes()) - - return Ok(None) diff --git a/donna/workspaces/config.py b/donna/workspaces/config.py index 26521a6..ee1364b 100644 --- a/donna/workspaces/config.py +++ b/donna/workspaces/config.py @@ -97,7 +97,6 @@ class Config(BaseEntity): _worlds_instances: list[BaseWorld] = pydantic.PrivateAttr(default_factory=list) _sources_instances: list[SourceConfigValue] = pydantic.PrivateAttr(default_factory=list) - tmp_dir: pathlib.Path = pathlib.Path("./tmp") cache_lifetime: float = 1.0 def model_post_init(self, __context: Any) -> None: # noqa: CCR001 diff --git a/donna/workspaces/tmp.py b/donna/workspaces/tmp.py deleted file mode 100644 index e89e21e..0000000 --- a/donna/workspaces/tmp.py +++ /dev/null @@ -1,51 +0,0 @@ -import pathlib -import shutil -import time - -from donna.cli.types import FullArtifactIdArgument -from donna.workspaces.config import config, config_dir - - -def dir() -> pathlib.Path: - cfg = config() - tmp_path = cfg.tmp_dir - - if not cfg.tmp_dir.is_absolute(): - tmp_path = config_dir() / tmp_path - - tmp_path.mkdir(parents=True, exist_ok=True) - - return tmp_path - - -def file_for_artifact(artifact_id: FullArtifactIdArgument, extension: str) -> pathlib.Path: - directory = dir() - - directory.mkdir(parents=True, exist_ok=True) - - normalized_extension = extension.lstrip(".") - artifact_file_name = f"{str(artifact_id).replace('/', '.')}.{int(time.time() * 1000)}.{normalized_extension}" - - return directory / artifact_file_name - - -def file_for_slug(slug: str, extension: str) -> pathlib.Path: - directory = dir() - - directory.mkdir(parents=True, exist_ok=True) - - normalized_slug = slug.replace("/", ".").replace("\\", ".") - normalized_extension = extension.lstrip(".") - artifact_file_name = f"{normalized_slug}.{int(time.time() * 1000)}.{normalized_extension}" - - return directory / artifact_file_name - - -def create_file_for_slug(slug: str, extension: str) -> pathlib.Path: - path = file_for_slug(slug, extension) - path.touch() - return path - - -def clear() -> None: - shutil.rmtree(dir()) diff --git a/donna/workspaces/worlds/base.py b/donna/workspaces/worlds/base.py index 4061e5a..3e79db8 100644 --- a/donna/workspaces/worlds/base.py +++ b/donna/workspaces/worlds/base.py @@ -42,9 +42,6 @@ def fetch(self, artifact_id: ArtifactId) -> Result[RawArtifact, ErrorsList]: ... def has_artifact_changed(self, artifact_id: ArtifactId, since: Milliseconds) -> Result[bool, ErrorsList]: pass - @abstractmethod - def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]: ... # noqa: E704 - @abstractmethod def list_artifacts(self, pattern: FullArtifactIdPattern) -> list[ArtifactId]: ... # noqa: E704 diff --git a/donna/workspaces/worlds/filesystem.py b/donna/workspaces/worlds/filesystem.py index 1c15c99..2ed1bf1 100644 --- a/donna/workspaces/worlds/filesystem.py +++ b/donna/workspaces/worlds/filesystem.py @@ -118,14 +118,6 @@ def has_artifact_changed(self, artifact_id: ArtifactId, since: Milliseconds) -> return Ok((path.stat().st_mtime_ns // 1_000_000) > since) - @unwrap_to_error - def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]: - path = self._resolve_artifact_file(artifact_id).unwrap() - if path is None: - return Err([world_errors.ArtifactNotFound(artifact_id=artifact_id, world_id=self.id)]) - - return Ok(path.suffix) - def read_state(self, name: str) -> Result[bytes | None, ErrorsList]: if not self.session: return Err([world_errors.WorldStateStorageUnsupported(world_id=self.id)]) diff --git a/donna/workspaces/worlds/python.py b/donna/workspaces/worlds/python.py index 65df815..f0569f7 100644 --- a/donna/workspaces/worlds/python.py +++ b/donna/workspaces/worlds/python.py @@ -135,14 +135,6 @@ def fetch(self, artifact_id: ArtifactId) -> Result[RawArtifact, ErrorsList]: # def has_artifact_changed(self, artifact_id: ArtifactId, since: Milliseconds) -> Result[bool, ErrorsList]: return Ok(False) - @unwrap_to_error - def file_extension_for(self, artifact_id: ArtifactId) -> Result[str, ErrorsList]: - resource_path = self._resolve_artifact_file(artifact_id).unwrap() - if resource_path is None: - return Err([world_errors.ArtifactNotFound(artifact_id=artifact_id, world_id=self.id)]) - - return Ok(pathlib.Path(resource_path.name).suffix) - def list_artifacts(self, pattern: FullArtifactIdPattern) -> list[ArtifactId]: # noqa: CCR001 return list_artifacts_by_pattern( world_id=self.id, From 7e40e693a9e2b5c3221b9ae2452efd6e83a24b1c Mon Sep 17 00:00:00 2001 From: "Aliaksei Yaletski (Tiendil)" Date: Wed, 1 Apr 2026 14:56:42 +0200 Subject: [PATCH 2/2] wip --- changes/unreleased.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changes/unreleased.md b/changes/unreleased.md index 87175dd..211c857 100644 --- a/changes/unreleased.md +++ b/changes/unreleased.md @@ -9,6 +9,7 @@ - Removed `donna artifacts` mutation commands and the supporting artifact-mutation code paths. - Removed `readonly` world-artifact mutability modeling from workspace config and world abstractions. - Updated artifact and world usage specs to state that developers and external tools mutate world artifacts while Donna validates them. +- Removed `donna artifacts fetch` and `donna artifacts tmp` commands and all related code. ### Breaking Changes