# Unified Sandbox Interface

## Overview

All sandbox implementations (InProcess, Subprocess, E2B) now share a consistent interface for context management, making it easy to switch between implementations.

## Core Interface

Every sandbox implementation must provide:

### 1. `__init__(timeout: int = 10)`
- Initialize the sandbox with a timeout
- Create `self.namespace` dict for storing variables (except E2B which uses remote namespace)
- Create `self._lock = asyncio.Lock()` for thread safety

### 2. `async run(code: str) -> SandboxResult`
- Execute Python code with timeout
- Wrapped in `async with self._lock:` for thread safety
- Returns standardized `SandboxResult` dict

### 3. `async get_context() -> str`
- Extract current namespace variables
- Filter to only picklable objects
- Return base64-encoded cloudpickle string

### 4. `async update_context(context: str | dict[str, Any])`
- Update namespace with variables
- Accept either:
  - `str`: base64-encoded cloudpickle string (from `get_context()`)
  - `dict`: direct variable dict

### 5. `_filter_picklable(namespace: dict) -> dict` (InProcess & Subprocess only)
- Filter namespace to only picklable user-defined objects
- Exclude: dunder variables, built-ins, coroutines, system resources

## Implementation Comparison

| Feature | InProcessSandbox | SubprocessSandbox | E2BSandbox |
|---------|------------------|-------------------|------------|
| Execution | In-process Python | Subprocess worker | Cloud E2B kernel |
| Namespace | `self.namespace` | `self.namespace` | Remote (E2B) |
| Context Storage | Local dict | Local dict | Remote kernel |
| Filtering | `_filter_picklable()` | `_filter_picklable()` | Remote filtering |
| Thread Safety | `asyncio.Lock` | `asyncio.Lock` | `asyncio.Lock` |
| Isolation | None | Process-level | Full cloud isolation |

## Example Usage

All sandboxes use the same API:

```python
# Create sandbox (any implementation)
sandbox = InProcessSandbox(timeout=30)
# OR
sandbox = SubprocessSandbox(timeout=30)
# OR
sandbox = E2BSandbox(timeout=30)

# Load previous context
await sandbox.update_context(previous_context_string)

# Execute code
result = await sandbox.run("x = 42")

# Get updated context
new_context = await sandbox.get_context()

# Can be used across turns/instances
await sandbox2.update_context(new_context)
```

## Context Flow

```
┌─────────────────────────────────────────────┐
│ All Sandboxes Follow Same Pattern          │
├─────────────────────────────────────────────┤
│ 1. update_context(previous_state)          │
│    → Deserialize and load into namespace   │
│                                             │
│ 2. run(code)                                │
│    → Execute with lock                      │
│    → Update namespace with new vars        │
│                                             │
│ 3. get_context()                            │
│    → Filter picklable objects              │
│    → Serialize to base64 string            │
│    → Return for storage in state           │
└─────────────────────────────────────────────┘
```

## Thread Safety

All sandboxes use `asyncio.Lock` to serialize execution:

```python
async with self._lock:
    # Only one execution at a time
    # Prevents race conditions on namespace
    # Prevents stdout redirection conflicts
    result = await self.run(code)
```

This ensures:
- No concurrent modifications to namespace
- No stdout/stderr conflicts
- Predictable execution order

## Filtering Logic

InProcessSandbox and SubprocessSandbox use the same filtering:

**Excluded**:
- Dunder variables (`__xxx__`)
- Built-in names (from `builtins`)
- Coroutines and async generators
- System resources: `ModuleType`, locks, sockets, file handles, thread objects
- Unpicklable objects (detected by attempting `pickle.dumps()`)

**Included**:
- User-defined variables
- User-defined functions (sync and async)
- User-defined classes and instances
- Module references (e.g., `import math` → `math` binding)
- All primitive types and collections

## Migration Between Sandboxes

To switch from one sandbox to another:

```python
# Before: Using InProcessSandbox
from universal_mcp.agents.Sandbox.in_process_sandbox import InProcessSandbox
sandbox = InProcessSandbox(timeout=30)

# After: Switch to SubprocessSandbox (better isolation)
from universal_mcp.agents.Sandbox.subprocess_sandbox import SubprocessSandbox
sandbox = SubprocessSandbox(timeout=30)
# Same API, no code changes needed!

# Or: Switch to E2B (production)
from universal_mcp.agents.Sandbox.e2b_sandbox import E2BSandbox
sandbox = E2BSandbox(timeout=30)
# Same API, no code changes needed!
```

## Best Practices

1. **Always await async methods**: `get_context()`, `update_context()`, `run()`
2. **Load context once per turn**: In `route_entry` node, not per execution
3. **Use consistent timeout**: Pass timeout to constructor
4. **Trust the interface**: All sandboxes behave the same externally
5. **Don't access namespace directly**: Use `get_context()` and `update_context()`

## Implementation Notes

### InProcessSandbox
- Fastest (no subprocess overhead)
- No isolation (code runs in same process)
- Best for development and testing
- Uses lock to prevent race conditions

### SubprocessSandbox
- Good isolation (separate process)
- Moderate overhead (subprocess spawn)
- Better for untrusted code
- Uses worker module for execution
- Uses lock to prevent race conditions

### E2BSandbox
- Full isolation (cloud environment)
- Network latency overhead
- Best for production
- Requires E2B API key
- Uses lock to prevent race conditions

## Troubleshooting

### Context Not Persisting
- Check that `update_context()` is called in `route_entry`
- Verify `get_context()` result is stored in state
- Ensure same sandbox instance or same memory/thread_id

### Unpicklable Object Errors
- Objects are automatically filtered by `_filter_picklable()`
- Check logs for warnings about unpicklable objects
- System resources (locks, sockets) are intentionally excluded

### Lock Timeout
- If execution hangs, check for infinite loops in code
- Lock is per-sandbox instance, not global
- Multiple sandbox instances can run in parallel

## Future Enhancements

Potential improvements while maintaining interface:

1. **Compression**: Add gzip compression to context strings
2. **Selective Persistence**: Allow marking objects as ephemeral
3. **Context Versioning**: Track cloudpickle version
4. **Size Limits**: Warn when context exceeds threshold
5. **Context Inspection**: Tools to view what's persisted
