feat(backends): rename MergedReadOnlyBackend to MergedSkillsBackend a… (#157)
* feat(backends): rename MergedReadOnlyBackend to MergedSkillsBackend and update documentation for clarity refactor(paths): simplify ensure_dirs function to create only memory directory eagerly fix(prompts): update skills availability description for accuracy test(paths): adjust test to reflect skills directory creation on demand * refactor(tests): format assertion for skills directory existence in ensure_dirs test
This commit is contained in:
@@ -253,7 +253,7 @@ def _get_default_backend():
|
||||
"""Build the default composite backend from current paths."""
|
||||
from deepagents.backends import CompositeBackend, FilesystemBackend
|
||||
|
||||
from .backends import CustomSandboxBackend, MergedReadOnlyBackend
|
||||
from .backends import CustomSandboxBackend, MergedSkillsBackend
|
||||
|
||||
workspace_dir = str(_paths_mod.WORKSPACE_ROOT)
|
||||
set_active_workspace(workspace_dir)
|
||||
@@ -266,7 +266,7 @@ def _get_default_backend():
|
||||
virtual_mode=True,
|
||||
timeout=300,
|
||||
)
|
||||
sk_backend = MergedReadOnlyBackend(
|
||||
sk_backend = MergedSkillsBackend(
|
||||
primary_dir=user_skills_dir,
|
||||
global_dir=global_skills_dir,
|
||||
secondary_dir=SKILLS_DIR,
|
||||
@@ -367,7 +367,7 @@ def create_cli_agent(workspace_dir: str | None = None, checkpointer=None, config
|
||||
from deepagents.backends import CompositeBackend, FilesystemBackend
|
||||
|
||||
from . import paths as _paths
|
||||
from .backends import CustomSandboxBackend, MergedReadOnlyBackend
|
||||
from .backends import CustomSandboxBackend, MergedSkillsBackend
|
||||
from .middleware import (
|
||||
ContextOverflowMapperMiddleware,
|
||||
ToolErrorHandlerMiddleware,
|
||||
@@ -407,7 +407,7 @@ def create_cli_agent(workspace_dir: str | None = None, checkpointer=None, config
|
||||
virtual_mode=True,
|
||||
timeout=300,
|
||||
)
|
||||
sk_backend = MergedReadOnlyBackend(
|
||||
sk_backend = MergedSkillsBackend(
|
||||
primary_dir=_usr_skills_dir,
|
||||
global_dir=_global_skills_dir,
|
||||
secondary_dir=SKILLS_DIR,
|
||||
|
||||
+11
-19
@@ -286,16 +286,17 @@ class ReadOnlyFilesystemBackend(FilesystemBackend):
|
||||
)
|
||||
|
||||
|
||||
class MergedReadOnlyBackend(BackendProtocol):
|
||||
"""Read-only backend that merges up to three skill directories.
|
||||
class MergedSkillsBackend(BackendProtocol):
|
||||
"""Skills backend that merges up to three skill directories.
|
||||
|
||||
Priority (high → low):
|
||||
1. primary — workspace/skills/ (project-local)
|
||||
2. global — ~/.config/evoscientist/skills/ (user global, optional)
|
||||
3. secondary — EvoScientist/skills/ (built-in, PyPI)
|
||||
1. primary — workspace/skills/ (project-local, writable)
|
||||
2. global — ~/.config/evoscientist/skills/ (user global, read-only)
|
||||
3. secondary — EvoScientist/skills/ (built-in, PyPI, read-only)
|
||||
|
||||
Higher-priority skills override lower-priority skills with the same name.
|
||||
All directories share the same virtual path namespace (/skills/).
|
||||
Only the workspace tier (primary) allows write and edit operations.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -304,9 +305,7 @@ class MergedReadOnlyBackend(BackendProtocol):
|
||||
secondary_dir: str,
|
||||
global_dir: str | None = None,
|
||||
):
|
||||
self._primary = ReadOnlyFilesystemBackend(
|
||||
root_dir=primary_dir, virtual_mode=True
|
||||
)
|
||||
self._primary = FilesystemBackend(root_dir=primary_dir, virtual_mode=True)
|
||||
self._global = (
|
||||
ReadOnlyFilesystemBackend(root_dir=global_dir, virtual_mode=True)
|
||||
if global_dir
|
||||
@@ -375,12 +374,10 @@ class MergedReadOnlyBackend(BackendProtocol):
|
||||
pass
|
||||
return GlobResult(matches=sorted(merged.values(), key=lambda x: x["path"]))
|
||||
|
||||
# -- write / edit: blocked --
|
||||
# -- write / edit: only workspace/skills/ (primary) is writable --
|
||||
|
||||
def write(self, file_path: str, content: str) -> WriteResult:
|
||||
return WriteResult(
|
||||
error="This directory is read-only. Write operations are not permitted here."
|
||||
)
|
||||
return self._primary.write(file_path, content)
|
||||
|
||||
def edit(
|
||||
self,
|
||||
@@ -389,9 +386,7 @@ class MergedReadOnlyBackend(BackendProtocol):
|
||||
new_string: str,
|
||||
replace_all: bool = False,
|
||||
) -> EditResult:
|
||||
return EditResult(
|
||||
error="This directory is read-only. Edit operations are not permitted here."
|
||||
)
|
||||
return self._primary.edit(file_path, old_string, new_string, replace_all)
|
||||
|
||||
# -- download / upload --
|
||||
|
||||
@@ -410,10 +405,7 @@ class MergedReadOnlyBackend(BackendProtocol):
|
||||
return responses
|
||||
|
||||
def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
|
||||
return [
|
||||
FileUploadResponse(path=path, error="permission_denied")
|
||||
for path, _ in files
|
||||
]
|
||||
return self._primary.upload_files(files)
|
||||
|
||||
|
||||
class CustomSandboxBackend(LocalShellBackend):
|
||||
|
||||
@@ -62,17 +62,15 @@ def set_workspace_root(path: str | Path) -> None:
|
||||
|
||||
|
||||
def ensure_dirs() -> None:
|
||||
"""Create runtime subdirectories (memory, skills) if they do not exist.
|
||||
"""Create runtime subdirectories if they do not exist.
|
||||
|
||||
Only memory is created eagerly — skills directories are created on demand
|
||||
by install_skill() when the user first installs a skill.
|
||||
|
||||
Does NOT create the workspace root itself — it should already exist
|
||||
(either the user's cwd or a directory they specified).
|
||||
"""
|
||||
for path in (MEMORY_DIR, USER_SKILLS_DIR):
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
GLOBAL_SKILLS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
except PermissionError:
|
||||
pass # read-only environments — skip silently
|
||||
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def default_workspace_dir() -> Path:
|
||||
|
||||
@@ -15,7 +15,7 @@ into reproducible experiments and a paper-ready experimental report.
|
||||
- Never invent results. If you cannot run something, say so and propose the smallest next step.
|
||||
- Delegate aggressively using the `task` tool. Prefer the research sub-agent for web search.
|
||||
- Use local skills when they match the task. Your available skills are listed in the system prompt — read the relevant `SKILL.md` for full instructions.
|
||||
All skills are available under `/skills/` (read-only).
|
||||
All skills are available under `/skills/`.
|
||||
|
||||
## Research Lifecycle (when applicable)
|
||||
For end-to-end research projects, the recommended skill sequence is:
|
||||
|
||||
+3
-1
@@ -102,4 +102,6 @@ class TestEnsureDirsUsesUpdatedPaths:
|
||||
paths.ensure_dirs()
|
||||
|
||||
assert (new_root / "memory").is_dir()
|
||||
assert (new_root / "skills").is_dir()
|
||||
assert not (
|
||||
new_root / "skills"
|
||||
).exists() # skills created on demand by install_skill()
|
||||
|
||||
Reference in New Issue
Block a user