In-memory overlay
Local sandbox честный: правит ваши файлы по-настоящему. Для «пусть
поисследует, но не сломает репо» нужен другой бэкенд. В оригинале
курса — just-bash (Node). У нас Python: копируем проект
во временную папку, гоняем local sandbox на копии, в конце — в мусор.
Не магия overlay, зато понятно и работает.
sandbox_memory.py + переключатель
SANDBOX=local|memory. Один промпт — два бэкенда, tools
те же.
Слова, которые встретятся
- Copy-on-write (упрощённо) — читаем оригинал, пишем в копию. Оригинал на диске не меняется.
-
Overlay — слой поверх реальных файлов. Наш вариант:
полная копия в
/tmp/….
Быстрый путь
- Создать
sandbox_memory.py— копия + local + cleanup. - В
main.pyвыбирать бэкенд поos.environ["SANDBOX"]. - Сравнить один промпт на
localиmemory.
Какие файлы трогаем
| Файл | Что делать |
|---|---|
sandbox_memory.py |
Создать. create_memory_sandbox(source_dir). |
main.py |
Править. Фабрика create_sandbox(cwd) по env. |
sandbox_local.py |
Не трогать — memory его переиспользует. |
Шаг 1. Memory-бэкенд
sandbox_memory.py
import shutil
import tempfile
from dataclasses import dataclass
from pathlib import Path
from sandbox_local import LocalSandbox, create_local_sandbox
@dataclass
class MemorySandbox:
type: str
working_directory: str
_inner: LocalSandbox
_tmp_root: Path
async def read_file(self, path: str) -> str:
return await self._inner.read_file(path)
async def exec(self, command: str):
return await self._inner.exec(command)
async def stop(self) -> None:
await self._inner.stop()
shutil.rmtree(self._tmp_root, ignore_errors=True)
def create_memory_sandbox(source: Path) -> MemorySandbox:
tmp_root = Path(tempfile.mkdtemp(prefix="harness-mem-"))
project = tmp_root / "project"
shutil.copytree(
source.resolve(),
project,
ignore=shutil.ignore_patterns(".venv", "__pycache__", ".git"),
dirs_exist_ok=True,
)
inner = create_local_sandbox(project)
return MemorySandbox(
type="memory",
working_directory=inner.working_directory,
_inner=inner,
_tmp_root=tmp_root,
)
.venv не копируем — тяжело и бессмысленно. Агент
всё равно не должен ставить пакеты без спроса (Guardrails).
Шаг 2. Переключатель в main.py
import os
from sandbox_local import create_local_sandbox
from sandbox_memory import create_memory_sandbox
def create_sandbox(cwd: Path):
mode = os.environ.get("SANDBOX", "local")
if mode == "memory":
return create_memory_sandbox(cwd)
return create_local_sandbox(cwd)
sandbox = create_sandbox(cwd)
print(f"Sandbox: {sandbox.type}", file=sys.stderr)
/tmp.
Для обучения достаточно: «агент не трогает исходный каталог».
Проверьте
Один и тот же read:
uv run python main.py . "Прочитай AGENTS.md"
SANDBOX=memory uv run python main.py . "Прочитай AGENTS.md"
Ответы совпадают; stderr: Sandbox: local vs
Sandbox: memory.
Эксперимент с записью (когда появится write, или руками
через bash если approval пускает):
SANDBOX=memory uv run python main.py . "Создай файл scratch.txt через echo hello > scratch.txt"
В копии файл есть; в исходной папке проекта — нет (проверьте
ls scratch.txt снаружи).
Готово, если:
- Есть
create_memory_sandbox SANDBOX=memoryпереключает бэкенд без правки tools- Read на обоих бэкендах даёт тот же текст
stop()удаляет временную папку