Mocking
Give an agent fake data in place of a real dependency and it can't tell the difference. Hiver lets you mock at two layers, both transparent to the code running in the sandbox:
- Mock a file system — back a mount with a small HTTP host that serves canned files.
- Mock an API — re-route the agent's outbound requests to a stub that returns canned responses.
Both are ideal for tests, offline development, and giving an agent deterministic inputs without provisioning real storage or paying for real API calls. They're especially useful for evals: pin the agent's inputs and API responses so every run starts from the same fixed environment, then grade the agent's behavior against a known-correct outcome, reproducibly and without real-world side effects.
Mock a file system
The external backend is the easy way to hand an agent canned files: point the mount at a small HTTP host that serves fixtures from memory instead of a real store. The agent reads deterministic data as ordinary files.
The host answers a handful of endpoints keyed by a path (or prefix) query parameter. A read-only mock only needs the read side:
| Request | Returns |
|---|---|
GET /v1/stat?path= | { path, size, mtime, is_dir } for one entry, or 404 if it's absent. |
GET /v1/file?path= | The file's raw bytes (application/octet-stream). |
GET /v1/directory?path= | { entries: [{ path, size, mtime, is_dir }] }, a directory's immediate children. |
GET /v1/list?prefix= | { paths: [...] }, every path under a prefix. |
A writable mock also handles PUT /v1/file?path=, DELETE /v1/file?path=, and POST /v1/move ({ src, dst }); reply 404 with a { "error": "..." } body for anything missing. Here's a complete read-only mock serving two fixtures:
import { createServer, type ServerResponse } from "node:http";
// Canned files the agent will see under the mount, keyed by mount-relative path.
const files: Record<string, Buffer> = {
"/pricing.json": Buffer.from(JSON.stringify({ plan: "pro", price: 20 })),
"/regions.txt": Buffer.from("us-east\nus-west\neu-central\n"),
};
const send = (res: ServerResponse, code: number, body: unknown) => {
res.writeHead(code, { "content-type": "application/json" });
res.end(JSON.stringify(body));
};
const info = (p: string) => ({ path: p, size: files[p].length, mtime: new Date().toISOString(), is_dir: false });
createServer((req, res) => {
const url = new URL(req.url!, "http://localhost");
const p = url.searchParams.get("path") ?? "/";
if (url.pathname === "/v1/file" && files[p]) {
res.writeHead(200, { "content-type": "application/octet-stream" });
return res.end(files[p]);
}
if (url.pathname === "/v1/stat") {
if (files[p]) return send(res, 200, info(p));
if (p === "/") return send(res, 200, { path: "/", size: 0, mtime: new Date().toISOString(), is_dir: true });
}
if (url.pathname === "/v1/directory") return send(res, 200, { entries: Object.keys(files).map(info) });
if (url.pathname === "/v1/list") return send(res, 200, { paths: Object.keys(files) });
send(res, 404, { error: "not found" }); // read-only: no PUT/DELETE/move
}).listen(7070, () => console.log("mock fs on :7070"));Mount it read-only and the agent reads the fixtures as ordinary files. On the local Docker runtime, run the mock on your host and add an extra_hosts entry so the sandbox can resolve it, host-gateway resolves to the Docker host:
import { getOrCreateSandbox } from "@hiver.sh/client";
const sandbox = await getOrCreateSandbox("mock-demo", {
image: "python",
fs: [{
backend: "external",
mount: "/fixtures",
host: "http://mock-fs:7070",
acls: [{ path: "/fixtures/**", access: "ro" }],
}],
extra_hosts: ["mock-fs:host-gateway"], // reach the mock running on your host
});
const { stdout } = await sandbox.exec(["cat", "/fixtures/pricing.json"]);
console.log(stdout); // {"plan":"pro","price":20}Because the mount is just HTTP, the same host can grow into a real backend later, swap the in-memory map for a database or object store and the sandbox side doesn't change.
Mock an API
Re-routing a host with a network override points an agent at a mock API. The agent calls the real service by name; the proxy quietly sends every matching request to a stub you control, which returns canned responses. The agent's code is unchanged and it never learns it's talking to a mock.
Run a small stub that answers with fixed data:
import { createServer } from "node:http";
// Canned response for every request, regardless of path.
createServer((_req, res) => {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ USD: 1, EUR: 0.92 }));
}).listen(8080, () => console.log("mock api on :8080"));Allow the real host and override its host to your stub. As above, add an extra_hosts entry so the proxy can resolve the stub on the local Docker runtime:
const sandbox = await getOrCreateSandbox("mock-demo", {
image: "python",
egress: [{
access: "allow",
host: "rates.example.com",
override: { host: "mock-api:8080" }, // re-route matching requests to the stub
}],
extra_hosts: ["mock-api:host-gateway"], // reach the stub running on your host
});
// The agent calls the real host; the proxy answers from the stub.
const { stdout } = await sandbox.exec([
"python3", "-c",
"import urllib.request as u; print(u.urlopen('http://rates.example.com/latest').read().decode())",
]);
console.log(stdout); // {"USD":1,"EUR":0.92}override and override_script both depend on this interception, so they require SandboxConfig.mitm to stay at its default of true. Setting mitm: false (see Network Access) disables TLS interception for the whole sandbox, which disables re-routing for HTTPS destinations too.
Next: File streaming