GitHub - shenli/ramet: Persistent, forkable workspaces for AI agents. Git-aware snapshots, verified resets, and control over what survives across sessions.

GitHub

7 min read Original article ↗

Persistent, forkable workspaces for AI agents.

Save and restore agent work across sessions and sandbox replacements. Fork isolated attempts, reset to a clean starting point, and preserve the results you choose.

Ramet gives each session or attempt an isolated writable workspace from a shared immutable base. Changes become persistent when your harness explicitly captures a snapshot or promotes selected state.

Bring your own agent framework and sandbox. Your framework coordinates the work, your sandbox runs it, and Ramet manages the workspace lifecycle.

Problem you need to solve What Ramet handles
Keep agent files when a sandbox is replaced Save and restore workspace files and local Git state across sessions.
Resume document-generation tasks Checkpoint PDFs, documents, spreadsheets, and presentations; reuse unchanged chunks of large files. Artifact checkpoints.
Continue work without reinstalling compatible dependencies Restore a workspace with a pinned runtime image and check dependencies before reuse. Session recovery.
Give parallel agents isolated workspaces Independent attempts from one base, local forks, and selective persistence for best-of-N coding agents.
Reset RL environments between rollouts Clean filesystem resets and verification before workspace reuse to catch contamination.
Run repeatable agent evaluations Reuse prepared repositories and dependencies through shared content, caches, and warm pools.

Using LangChain / LangGraph, CrewAI, AutoGen, or SkyRL?

Your stack Integration path
LangChain / LangGraph — persistent files for tool-using agents Custom tool/runtime integration; save a Ramet snapshot ID alongside your thread state.
CrewAI / AutoGen — isolated files for parallel coding agents Custom tool/executor integration; assign an execution per independent attempt.
SkyRL — workspaces for agentic RL rollouts Runnable SkyRL-Gym environment, tested with a real worker and Docker.
Your own agent or evaluation harness Python / Go / HTTP integration, with an included same-host Docker adapter.

LangChain/LangGraph, CrewAI, and AutoGen require application adapter code; dedicated Ramet plugins are not included. Your framework keeps conversation state, orchestration, rewards, and training. Ramet persists filesystem state; process memory and external services need their own recovery strategy.

Why workspace state needs different treatment

An agent workspace mixes files with very different lifetimes. A local commit may be the only copy of useful work; a dependency directory can be rebuilt; a scratch file may belong to just one attempt. Saving everything carries reproducible and temporary data into every saved result. Throwing everything away loses work you cannot reconstruct.

Ramet lets you express those differences when saving an attempt:

Kind of state Examples What should survive
Shared immutable Fixed datasets, prepared read-only inputs Reuse the unchanged base content across attempts.
Durable Source edits, local Git commits, selected outputs Preserve the current state so the work can be restored.
Rebuildable Dependencies, generated build outputs Keep a reconstruction recipe instead of the generated bytes.
Ephemeral Scratch files, temporary logs Leave them out of the saved result.

Your harness assigns path policies; Ramet does not infer which files are safe to drop. These policies apply on promotion, with unclassified paths preserved by default. Initial snapshots and checkpoints capture all supported files; your runtime executes reconstruction recipes. See persistence policies. Preview what will be saved before promotion, then restore through approved reconstruction steps with input checks and an application readiness check.

For parallel agents, this means sharing a prepared base, isolating each attempt's writes, and choosing both which attempt and which state to keep. Disposable attempts need no saved snapshot.

Development alpha · Linux & macOS · Apache-2.0

How it fits together

Framework integration calls Ramet through Python, Go, or HTTP. An immutable base supplies worker-local workspaces through copy/reflink or optional Linux FUSE. Your runtime attaches a local path and owns process cleanup. Captured snapshots persist locally or in PostgreSQL and S3.

Your harness calls Ramet through the Python SDK or Go client:

  1. Prepare: snapshot a project or import a Git revision, then create a pool.
  2. Acquire: get an isolated workspace and pass execution.path to your runtime as the sandbox's workspace mount.
  3. Run: use your existing framework to run the agent and evaluate its work.
  4. Save or recycle: stop the sandbox's writers and unmount the workspace; call promote() to save a selected result, then release the execution. Release resets the workspace for reuse.

The path belongs to the Ramet worker's filesystem. Deploy the worker beside the runtime that can mount it; a remote SDK connection does not transfer files to a remote sandbox. Your runtime owns process isolation, resource limits, and cleanup on lease loss.

How much integration work is involved?

Your environment Work to connect it
Same-host offline Docker commands Use the included adapter: acquire a workspace, call runtime.run(), then save or release. Add supervisor cleanup for harness crashes.
Your own container or VM runtime Change workspace allocation, mount the returned path, and implement runtime shutdown on timeout, lease loss, and cleanup failure.
Hosted sandbox without worker-path attachment Resolve filesystem attachment first; setting the SDK URL is not enough.

Your agent loop and evaluator stay in your framework. The integration guide shows the three call sites to change, an adapter sketch, and checks to validate your environment. The Python SDK guide walks through setup, Docker execution, persistence, and parallel attempts.

Execution writes stay local. Capture or promote explicitly to persist state. Start with local storage; add PostgreSQL and S3-compatible storage when snapshots need to survive worker-disk loss. Copy-on-write shares file data where supported, with independent full copies as the fallback. See the architecture and runtime security boundary. For large snapshots where attempts touch only a subset of files, the optional Linux FUSE backend hydrates working-tree files on first access. The included Docker adapter uses copy/reflink; FUSE needs custom mount attachment.

Quick start

This same-host setup introduces the API for local development and integration testing. A deployed worker uses the same lifecycle beside your sandbox runtime. Local storage keeps snapshots on that worker's disk; optional PostgreSQL and S3-compatible storage let a replacement worker restore published snapshots. See deployment and storage modes.

Requires Go 1.26.9+, Python 3.10+, Git, and Linux or macOS. Install the CLI and Python SDK, then start a local worker:

git clone https://github.com/shenli/ramet.git
cd ramet
go build -o bin/ramet ./cmd/ramet
python3 -m venv .venv
.venv/bin/python -m pip install ./sdk/python
./bin/ramet serve --data /tmp/ramet-data --listen 127.0.0.1:7433

Leave the worker running. In another terminal, from the same checkout, run the following with the absolute path to your project. Keep that project outside /tmp/ramet-data and stop its writers while capturing the snapshot.

.venv/bin/python - <<'PY'
from ramet import Ramet

ramet = Ramet()  # Connects to http://127.0.0.1:7433.
base = ramet.snapshot(source="/absolute/path/to/your/project")

with ramet.execution_pool(base, concurrency=4) as pool:
    with pool.acquire() as execution:
        # Run your tools against this directory; the source project is untouched.
        print("Workspace:", execution.path)
        (execution.path / "result.txt").write_text("selected result\n")
        saved = execution.promote()  # Persist this attempt as a new snapshot.
    # Leaving the execution context resets and releases its workspace.

with ramet.execution_pool(saved, concurrency=1) as pool:
    with pool.acquire() as restored:
        print((restored.path / "result.txt").read_text())
PY

The restored workspace prints selected result. Snapshots persist in the worker's data directory (use a persistent directory instead of /tmp to retain them long term); pools and execution directories are temporary. The SDK and worker run on the same host for this example. When connecting your runtime, mount only execution.path and stop its writers before promotion or release; the Docker adapter handles this lifecycle for container commands.

For ready-made demonstrations, run ./bin/ramet demo or follow the best-of-N coding demo.

Documentation

Licensed under Apache-2.0. See third-party notices.