Core Concepts¶
This guide explains the key concepts in AgentFense: Codebase, Sandbox, Permission, Session, Preset, and Delta Layer. Understanding these concepts helps you design effective permission policies and use AgentFense efficiently.
Codebase¶
A Codebase is file storage on the server—the source of truth for your code and data.
What It Does¶
- Stores files uploaded from local directories or created programmatically
- Provides versioned, immutable file storage (files cannot be modified in place)
- Serves as the data source for one or more Sandboxes
- Tracks metadata: file count, total size, owner, timestamps
Key Characteristics¶
| Characteristic | Description |
|---|---|
| Persistent | Files persist even after all Sandboxes are destroyed |
| Shareable | Multiple Sandboxes can use the same Codebase |
| Isolated | Each Codebase is isolated from others (no cross-contamination) |
Lifecycle¶
When to Use Multiple Codebases¶
- One codebase per project: Store all files for a single project
- Versioned codebases: Create new codebase for each release or snapshot
- Multi-tenancy: Separate codebase per user or team
Example¶
from agentfense import SandboxClient
client = SandboxClient(endpoint="localhost:9000")
# Create a codebase
codebase = client.create_codebase(
name="my-app-v1.0",
owner_id="team_123",
)
# Upload files
client.upload_file(codebase.id, "app.py", b"print('v1.0')")
client.upload_file(codebase.id, "config.yaml", b"version: 1.0")
# List files
files = client.list_files(codebase.id, path="/", recursive=True)
for f in files:
print(f"{f.path} ({f.size} bytes)")
# Download a file
content = client.download_file(codebase.id, "app.py")
print(content.decode())
# Clean up (only after destroying all sandboxes)
client.delete_codebase(codebase.id)
High-Level API (Automatic Codebase Management)¶
When using Sandbox.from_local(), codebases are created and deleted automatically:
from agentfense import Sandbox
# Codebase is created, used, and deleted automatically
with Sandbox.from_local("./my-project") as sandbox:
result = sandbox.run("ls /workspace")
# Codebase deleted here
Sandbox¶
A Sandbox is an isolated execution environment where untrusted code runs against a Codebase with enforced permissions.
What It Does¶
- Mounts a Codebase as a FUSE filesystem at
/workspace - Enforces permission rules at the filesystem level
- Provides process isolation (bwrap or Docker)
- Captures writes to a per-sandbox Delta Layer (Copy-On-Write)
- Executes commands with controlled environment, working directory, and timeout
Architecture¶
┌─────────────────────────────────────────┐
│ Sandbox Process │
│ (bwrap namespace / Docker container) │
├─────────────────────────────────────────┤
│ FUSE Filesystem Mount │
│ /workspace (enforced) │
├─────────────────────────────────────────┤
│ Delta Layer (COW) │
│ (per-sandbox write isolation) │
├─────────────────────────────────────────┤
│ Codebase Storage │
│ (immutable source files) │
└─────────────────────────────────────────┘
Lifecycle¶
Sandbox States¶
| State | Description | Available Operations |
|---|---|---|
| PENDING | Created but not started | Start, Destroy |
| RUNNING | Active and ready for commands | Exec, Session, Stop, Destroy |
| STOPPED | Stopped but not destroyed | Start, Destroy |
| ERROR | Failed to start or crashed | Destroy |
Runtime Types¶
AgentFense supports two isolation mechanisms:
| Runtime | Isolation | Startup Time | Use Case |
|---|---|---|---|
| bwrap | Linux namespaces (lightweight) | ~50ms | Fast iteration, development, low overhead |
| Docker | Container (strong isolation) | ~500ms | Production, strict isolation, custom images |
Resource Limits¶
Control resource usage with ResourceLimits:
from agentfense import Sandbox, RuntimeType, ResourceLimits
with Sandbox.from_local(
"./project",
runtime=RuntimeType.DOCKER,
image="python:3.11-slim",
resources=ResourceLimits(
memory_bytes=512 * 1024 * 1024, # 512 MB RAM
cpu_quota=50000, # 50% CPU (50000/100000)
pids_limit=100, # Max 100 processes
),
) as sandbox:
result = sandbox.run("python heavy_task.py")
Example: Manual Sandbox Lifecycle¶
from agentfense import SandboxClient
client = SandboxClient()
# Create sandbox
sandbox = client.create_sandbox(
codebase_id="cb_xxx",
permissions=[{"pattern": "**/*", "permission": "read"}],
)
# Start (allocates resources, mounts FUSE)
client.start_sandbox(sandbox.id)
# Execute commands
result = client.exec(sandbox.id, command="ls /workspace")
print(result.stdout)
# Stop (unmounts FUSE, stops processes)
client.stop_sandbox(sandbox.id)
# Destroy (cleans up all resources)
client.destroy_sandbox(sandbox.id)
Permission¶
Permissions define what an agent can do with each file or directory in the Codebase.
Four Permission Levels¶
AgentFense provides four levels of file access control:
| Level | Visibility | Read Content | Write/Modify | Example Use Case |
|---|---|---|---|---|
| none | ❌ Invisible | ❌ | ❌ | Hide secrets, credentials, private keys |
| view | ✅ In ls |
❌ | ❌ | Show file structure without exposing content |
| read | ✅ | ✅ | ❌ | Source code, documentation, logs (read-only) |
| write | ✅ | ✅ | ✅ | Output directories, temp files, generated reports |
Permission Patterns¶
Three types of patterns are supported:
| Type | Syntax | Example | Matches |
|---|---|---|---|
| glob | Wildcard patterns | **/*.py |
All Python files recursively |
/secrets/** |
All files under /secrets |
||
*.env* |
.env, .env.local, .env.production |
||
| directory | Path ending with / |
/docs/ |
All files in /docs and subdirectories |
| file | Exact file path | /config.yaml |
Only /config.yaml |
Priority Algorithm¶
When multiple rules match a path, AgentFense uses this deterministic priority:
- Explicit Priority (if set via
priorityfield) - Pattern Type:
file(3) >directory(2) >glob(1) - Pattern Specificity: More specific patterns win
Example:
permissions = [
{"pattern": "**/*", "permission": "read"}, # Priority: 0 (glob)
{"pattern": "/secrets/**", "permission": "none"}, # Priority: 0 (glob)
{"pattern": "/secrets/public.key", "permission": "read"}, # Priority: 0 (file)
]
# Result:
# - /secrets/private.key → none (matched by /secrets/**)
# - /secrets/public.key → read (file pattern overrides glob)
# - /app/main.py → read (matched by **/*)
Example: Building Permission Rules¶
from agentfense import Sandbox
# Scenario: Allow read all, write to /output, hide secrets
PERMISSIONS = [
# Base rule: read everything
{"pattern": "**/*", "permission": "read", "priority": 0},
# Allow writes to output directory
{"pattern": "/output/**", "permission": "write", "priority": 10},
# Hide secrets (higher priority overrides base)
{"pattern": "**/.env*", "permission": "none", "priority": 100},
{"pattern": "/secrets/**", "permission": "none", "priority": 100},
{"pattern": "**/*.key", "permission": "none", "priority": 100},
# Exception: public key should be readable
{"pattern": "/secrets/public.key", "permission": "read", "priority": 200},
]
with Sandbox.from_local("./project", permissions=PERMISSIONS) as sandbox:
# Can read most files
sandbox.run("cat /workspace/README.md") # ✅
# Can write to /output
sandbox.run("echo result > /workspace/output/log.txt") # ✅
# Cannot see private keys
sandbox.run("ls /workspace/secrets/") # public.key only (no private.key)
# Can read public key
sandbox.run("cat /workspace/secrets/public.key") # ✅
Session¶
A Session is a persistent shell process that maintains state across multiple command executions.
Why Use Sessions?¶
Without sessions, each exec() starts a new shell:
# Each command runs in a new shell (state is lost)
sandbox.run("cd /workspace/src") # Changes directory
sandbox.run("pwd") # Output: /workspace (NOT /workspace/src)
With sessions, state persists:
# Commands run in the same shell (state is preserved)
with sandbox.session() as session:
session.exec("cd /workspace/src")
result = session.exec("pwd")
print(result.stdout) # Output: /workspace/src ✅
What Sessions Preserve¶
- Working Directory:
cdcommands persist - Environment Variables:
export VAR=valuepersists - Shell State: Activated virtualenvs, sourced files
- Background Processes: Processes started with
&continue running
Example: Multi-Step Workflow¶
from agentfense import Sandbox
with Sandbox.from_local("./python-app") as sandbox:
with sandbox.session() as session:
# Setup environment
session.exec("cd /workspace")
session.exec("export DEBUG=1")
session.exec("source venv/bin/activate")
# Install dependencies (venv is active)
session.exec("pip install -r requirements.txt")
# Run tests (DEBUG=1 and venv still active)
result = session.exec("pytest tests/")
print(result.stdout)
Session Lifecycle¶
Sessions are automatically closed when:
- Context manager exits (with session:)
- Sandbox is stopped or destroyed
- Timeout occurs (configurable)
Low-Level Session API¶
client = SandboxClient()
# Create session
session = client.create_session(
sandbox_id=sandbox.id,
shell="/bin/bash", # Or /bin/sh, /bin/zsh
env={"PYTHONPATH": "/workspace/lib"},
)
# Execute commands
result = client.session_exec(session.id, command="cd /workspace")
result = client.session_exec(session.id, command="ls")
# Close session (optional - auto-closed when sandbox stops)
client.close_session(session.id)
Preset¶
Presets are pre-configured permission templates for common use cases.
Built-In Presets¶
| Preset | Description | Use Case |
|---|---|---|
| agent-safe | Read all, write to /output & /tmp, hide secrets |
Default for AI agents (safe exploration + controlled output) |
| read-only | Read all files, no write access | Code review, static analysis, safe browsing |
| full-access | Read and write all files | Trusted automation, testing, development |
| development | Full access except secrets | Local development with safety guardrails |
| view-only | See file names, cannot read content | Exploring file structure without exposing data |
Using Presets¶
from agentfense import Sandbox
# Use a preset directly
with Sandbox.from_local("./project", preset="agent-safe") as sandbox:
result = sandbox.run("ls")
Extending Presets¶
from agentfense import extend_preset
# Start with agent-safe, add custom rules
rules = extend_preset(
base="agent-safe",
additions=[
{"pattern": "/logs/**", "permission": "write"}, # Also allow log writes
],
overrides=[
{"pattern": "**/.git/**", "permission": "read"}, # Override: make .git readable
],
)
with Sandbox.from_local("./project", permissions=rules) as sandbox:
result = sandbox.run("echo log > /workspace/logs/app.log") # ✅
Creating Custom Presets¶
from agentfense import register_preset
# Define a custom preset
register_preset("ci-pipeline", [
{"pattern": "**/*", "permission": "read"},
{"pattern": "/build/**", "permission": "write"},
{"pattern": "/dist/**", "permission": "write"},
{"pattern": "**/node_modules/**", "permission": "none"},
{"pattern": "**/.env*", "permission": "none"},
])
# Use it like built-in presets
with Sandbox.from_local("./app", preset="ci-pipeline") as sandbox:
sandbox.run("npm run build")
Delta Layer (Copy-On-Write)¶
The Delta Layer provides write isolation when multiple Sandboxes share the same Codebase.
Problem: Write Conflicts¶
Without isolation, multiple Sandboxes writing to the same Codebase would conflict:
Sandbox A writes /workspace/output.txt → overwrites
Sandbox B writes /workspace/output.txt → conflict!
Solution: Per-Sandbox Delta Layer¶
Each Sandbox writes to its own delta directory:
Codebase (immutable): /var/lib/agentfense/codebases/cb_123/
Sandbox A delta: /var/lib/agentfense/mounts/sb_A/delta/
Sandbox B delta: /var/lib/agentfense/mounts/sb_B/delta/
How It Works¶
- Read: Check delta first, fallback to source Codebase
- Write: Always write to delta (source remains untouched)
- Delete: Create whiteout marker (
.wh.<filename>) - Sync: On exec completion, delta syncs to source (Last-Writer-Wins)
Behavior¶
| Operation | What Happens |
|---|---|
Read /workspace/file.txt |
If exists in delta, read from delta; else read from codebase |
Write /workspace/file.txt |
Write to delta/file.txt (codebase untouched) |
Delete /workspace/file.txt |
Create delta/.wh.file.txt (whiteout marker) |
| Exec completes | Sync delta → codebase (LWW if conflicts) |
Example¶
from agentfense import Sandbox
# Two sandboxes share the same codebase
with Sandbox.from_local("./project") as sandbox_a:
sandbox_a.run("echo A > /workspace/output.txt")
with Sandbox.from_local("./project") as sandbox_b:
sandbox_b.run("echo B > /workspace/output.txt")
# After both complete, codebase has:
# - output.txt with content "B" (Last-Writer-Wins)
When to Care About Delta Layer¶
- Multi-sandbox concurrency: Running multiple agents on the same codebase
- Write safety: Ensuring sandboxes don't corrupt each other's data
- Rollback: Delta changes are ephemeral (lost if sandbox crashes)
Disabling Sync (Ephemeral Writes)¶
If you don't want writes to persist to the codebase:
# Option 1: Use read-only preset
with Sandbox.from_local("./project", preset="read-only") as sandbox:
# All writes fail (permission denied)
pass
# Option 2: Allow writes but don't sync (custom server config)
# Set sync_on_completion=false in server config
Summary¶
| Concept | What It Is | Key Insight |
|---|---|---|
| Codebase | File storage on server | Immutable source of truth, shareable |
| Sandbox | Isolated execution environment | Enforces permissions at filesystem level |
| Permission | Access control rules | Four levels (none/view/read/write) with priority |
| Session | Persistent shell process | Preserves working directory and environment |
| Preset | Pre-configured permission template | Reusable patterns for common scenarios |
| Delta Layer | Per-sandbox write isolation | COW with sync-on-completion (LWW) |
Next Steps¶
- Installation Guide - Set up server and dependencies
- Security Model - How permissions are enforced
- Permission System - Deep dive into rules and patterns
- Presets Guide - Using and extending presets
- Best Practices - Production deployment patterns