MODULE_04 · УРОК 4.3

In-memory overlay

Local sandbox честный: правит ваши файлы по-настоящему. Для «пусть поисследует, но не сломает репо» нужен другой бэкенд. В оригинале курса — just-bash (Node). У нас Python: копируем проект во временную папку, гоняем local sandbox на копии, в конце — в мусор. Не магия overlay, зато понятно и работает.

ЧТО ПОЛУЧИТСЯ sandbox_memory.py + переключатель SANDBOX=local|memory. Один промпт — два бэкенда, tools те же.

Слова, которые встретятся

  • Copy-on-write (упрощённо) — читаем оригинал, пишем в копию. Оригинал на диске не меняется.
  • Overlay — слой поверх реальных файлов. Наш вариант: полная копия в /tmp/….

Быстрый путь

  1. Создать sandbox_memory.py — копия + local + cleanup.
  2. В main.py выбирать бэкенд по os.environ["SANDBOX"].
  3. Сравнить один промпт на 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)
ИДЕЯ ТА ЖЕ, ЧТО У just-bash В [оригинале](https://vercel.com/academy/build-ai-agent-harness/in-memory-implementation) виртуальный FS в RAM. Мы — копия на диск в /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 снаружи).

НЕ ВСЁ ПОРТИРУЕТСЯ С ПЕРВОГО РАЗА Абсолютные пути, хардкод cwd, странный grep — могут вести себя иначе на копии. Это нормальный «тест переносимости» интерфейса.

Готово, если:

  • Есть create_memory_sandbox
  • SANDBOX=memory переключает бэкенд без правки tools
  • Read на обоих бэкендах даёт тот же текст
  • stop() удаляет временную папку