Snapshotting in Bashkit
Bashkit can serialize an interpreter into opaque bytes and restore it later. Use snapshots for checkpoint/resume flows, warm sandbox caching, or rolling back to a known-good virtual workspace.
What a snapshot captures
- Shell state: variables, exported env, arrays, aliases, and current working directory
- Virtual filesystem contents by default
- Session counters used by interpreter limits
Pass snapshot options to skip VFS capture when you only want shell state.
restore_snapshot() preserves the current instance configuration such as limits,
builtins, and filesystem backend, then replaces shell state and VFS contents
with the snapshot. from_snapshot() creates a fresh instance from bytes.
In the Rust core, Bash::from_snapshot() returns a default-configured
interpreter. If you need custom limits, builtins, or filesystem wiring, build
that instance first and call restore_snapshot() on it.
Rust
use bashkit::{Bash, ExecutionLimits, SnapshotOptions};
# #[tokio::main]
# async fn main() -> bashkit::Result<()> {
let mut bash = Bash::new();
bash.exec("export BUILD_ID=42; mkdir -p /workspace && cd /workspace && echo ready > state.txt")
.await?;
let snapshot = bash.snapshot()?;
let shell_only = bash.snapshot_with_options(SnapshotOptions {
exclude_filesystem: true,
exclude_functions: false,
})?;
let mut restored = Bash::from_snapshot(&snapshot)?;
assert_eq!(restored.exec("echo $BUILD_ID").await?.stdout.trim(), "42");
assert_eq!(
restored.exec("cat /workspace/state.txt").await?.stdout.trim(),
"ready"
);
// Reuse an explicitly configured instance and preserve its limits.
let limits = ExecutionLimits::new().max_commands(100);
let mut configured = Bash::builder().limits(limits).build();
configured.restore_snapshot(&snapshot)?;
configured.restore_snapshot(&shell_only)?;
# Ok(())
# }
Python
Python exposes snapshotting on both Bash and BashTool:
from bashkit import Bash
bash = Bash(username="agent", max_commands=100)
bash.execute_sync(
"export BUILD_ID=42; mkdir -p /workspace && cd /workspace && echo ready > state.txt"
)
snapshot = bash.snapshot()
shell_only = bash.snapshot(exclude_filesystem=True)
prompt_only = bash.snapshot(exclude_filesystem=True, exclude_functions=True)
restored = Bash.from_snapshot(snapshot, username="agent", max_commands=100)
assert restored.execute_sync("echo $BUILD_ID").stdout.strip() == "42"
assert restored.execute_sync("cat /workspace/state.txt").stdout.strip() == "ready"
restored.reset()
restored.restore_snapshot(snapshot)
assert restored.execute_sync("pwd").stdout.strip() == "/workspace"
restored.restore_snapshot(shell_only)
Node.js / TypeScript
Node exposes snapshotting on Bash and BashTool:
import { Bash } from "@everruns/bashkit";
const bash = new Bash({ username: "agent", maxCommands: 100 });
bash.executeSync(
"export BUILD_ID=42; mkdir -p /workspace && cd /workspace && echo ready > state.txt",
);
const snapshot = bash.snapshot();
const shellOnly = bash.snapshot({ excludeFilesystem: true });
const promptOnly = bash.snapshot({
excludeFilesystem: true,
excludeFunctions: true,
});
const restored = Bash.fromSnapshot(snapshot, {
username: "agent",
maxCommands: 100,
});
if (restored.executeSync("echo $BUILD_ID").stdout.trim() !== "42") {
throw new Error("snapshot restore failed");
}
restored.reset();
restored.restoreSnapshot(snapshot);
restored.restoreSnapshot(shellOnly);
Security note
The default snapshot format includes integrity checks for accidental corruption,
but it does not authenticate untrusted bytes. If snapshots cross trust
boundaries such as shared storage or network transfer, use Rustβs keyed APIs
(snapshot_to_bytes_keyed, from_snapshot_keyed, restore_snapshot_keyed) or
treat the snapshot bytes as trusted-only input.