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:
Xi Zhang
2026-04-15 02:52:31 +02:00
committed by GitHub
parent 64721f5967
commit c4237fecb8
5 changed files with 24 additions and 32 deletions
+4 -4
View File
@@ -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
View File
@@ -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):
+5 -7
View File
@@ -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:
+1 -1
View File
@@ -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
View File
@@ -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()