Filesystem
An inferlet can read and write files inside a single sandboxed directory the host preopens at /scratch. Files persist across inferlet runs as long as the engine is up. This is the right place for episodic memory, checkpoints, and any state that should outlive an individual process. Read this after I/O overview.
The current filesystem story is intentionally narrow: a single guest path (/scratch) backed by one host directory the engine controls. A multi-mount, per-tenant table is on the roadmap; today the contract is "either filesystem access is enabled, or it isn't, and the inferlet sees one directory."
Read and write
The interface is the language's standard filesystem API.
- Rust
- Python
- JavaScript
let s: String = std::fs::read_to_string("/scratch/in.txt")
.map_err(|e| e.to_string())?;
std::fs::write("/scratch/out.json", &json_str)
.map_err(|e| e.to_string())?;
with open("/scratch/in.txt") as f:
s = f.read()
with open("/scratch/out.json", "w") as f:
f.write(json_str)
import { readFile, writeFile } from 'node:fs/promises';
const s = await readFile('/scratch/in.txt', 'utf-8');
await writeFile('/scratch/out.json', jsonStr);
Standard filesystem code works as written. The WASI sandbox redirects calls to the preopened directory; paths outside /scratch return permission errors.
Enable filesystem access
Filesystem access is gated by [runtime].allow_fs in ~/.pie/config.toml. It defaults off:
[runtime]
allow_fs = true
# fs_scratch_dir = "/tmp/pie" # optional override; default is <tempdir>/pie
When enabled, the engine creates a host-side scratch directory under [runtime].fs_scratch_dir and preopens a per-process subdirectory at /scratch for every inferlet on this server. Restart the engine to apply.
When disabled (the default), the inferlet's filesystem syscalls return errors immediately. Each inferlet gets its own subdirectory under the configured scratch root.
See Configuration for the full schema.
Patterns
Episodic memory
Every run appends a structured event to a JSONL file. The next run reads recent events back into context.
- Rust
- Python
- JavaScript
use std::io::Write;
let mut f = std::fs::OpenOptions::new()
.append(true).create(true)
.open("/scratch/memory.jsonl")
.map_err(|e| e.to_string())?;
writeln!(f, "{}", serde_json::json!({"role": "user", "text": question}).to_string())
.map_err(|e| e.to_string())?;
import json
with open("/scratch/memory.jsonl", "a") as f:
f.write(json.dumps({"role": "user", "text": question}) + "\n")
import { appendFile } from 'node:fs/promises';
await appendFile(
'/scratch/memory.jsonl',
JSON.stringify({ role: 'user', text: question }) + '\n',
);
Checkpoint and resume
Long-running optimization (e.g. ZO) checkpoints adapter weights and progress to disk. The next invocation resumes from the checkpoint.
adapter.save("/scratch/adapters/draft-v1.bin")?;
adapter.load("/scratch/adapters/draft-v1.bin")?;
See Adapters for the full lifecycle.
Per-inferlet subdirectories
Because every inferlet on the engine sees the same /scratch, build a stable per-inferlet directory yourself if you want isolation:
- Rust
- Python
- JavaScript
let dir = "/scratch/research-agent";
std::fs::create_dir_all(dir).map_err(|e| e.to_string())?;
std::fs::write(format!("{dir}/state.json"), &json_str)
.map_err(|e| e.to_string())?;
import os
os.makedirs("/scratch/research-agent", exist_ok=True)
with open("/scratch/research-agent/state.json", "w") as f:
f.write(json_str)
import { mkdir, writeFile } from 'node:fs/promises';
await mkdir('/scratch/research-agent', { recursive: true });
await writeFile('/scratch/research-agent/state.json', jsonStr);
This is a convention, not a sandbox. Other inferlets on the same engine can read and write the same paths.
Sandbox boundary
Inferlets cannot:
- Access paths outside
/scratch.std::fs::read("/etc/passwd")returnsErr(NotFound)(orPermissionDenied, depending on the platform). - Discover the host's directory structure. The only mount the inferlet sees is
/scratch. - Talk to other inferlets through the filesystem in any privileged sense — they share the same
/scratch, but the runtime does not enforce per-inferlet isolation today.
For the multi-tenant story (per-tenant mounts, read-only paths, host-controlled grants), see the runtime roadmap.
Next
- HTTP: the network alternative for state that lives elsewhere.
- MCP: structured tool servers, which often use the filesystem internally.
- Configuration: the
[runtime].allow_fsflag and the rest of the schema.