Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 0 additions & 58 deletions .github/workflows/docs.yml

This file was deleted.

8 changes: 4 additions & 4 deletions .github/workflows/pytorch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ jobs:
- name: Test example with PyTorch
run: |
source .venv/bin/activate
cd example
python main.py -v
cd example/torch_share
python host.py
test-pytorch-cuda:
name: Test with PyTorch CUDA
Expand Down Expand Up @@ -105,5 +105,5 @@ jobs:
- name: Test example with PyTorch
run: |
source .venv/bin/activate
cd example
python main.py -v
cd example/torch_share
python host.py
4 changes: 2 additions & 2 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ jobs:
- name: Test example
run: |
.venv\Scripts\activate
cd example
python main.py -v
cd example\torch_share
python host.py
test-windows-pytorch:
name: Test on Windows with PyTorch
Expand Down
194 changes: 94 additions & 100 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,130 +1,124 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Guidance for AI agents maintaining the pyisolate codebase.

## Project Overview
## Identity

**pyisolate** is a Python library for running extensions across multiple isolated virtual environments with RPC communication. It solves dependency conflicts by isolating extensions in separate venvs while maintaining seamless host-extension communication through AsyncRPC.
**pyisolate** is a Python library (PyPI: `pyisolate`, v0.10.1) for running extensions in isolated virtual environments with seamless inter-process communication. It provides dependency isolation, zero-copy tensor transfer, and bubblewrap sandboxing for GPU-heavy workloads.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded version will need updating with each release.

The version string "v0.10.1" is hardcoded and will become stale after the next release. Consider whether this maintenance burden is acceptable or if the version should be referenced dynamically or omitted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 7, The README hardcodes the pyisolate release string
"v0.10.1", which will become stale; update CLAUDE.md by removing the fixed
version token and either (a) omit the version entirely and keep "PyPI:
`pyisolate`", or (b) replace the literal "v0.10.1" with a dynamic reference such
as a PyPI/latest badge or wording like "latest" that links to the PyPI page;
update the line containing the symbol pyisolate and the string "v0.10.1"
accordingly.


## Development Commands

### Environment Setup
```bash
# Preferred (using uv - much faster)
uv venv && source .venv/bin/activate && uv pip install -e ".[dev,docs]"
pre-commit install

# With benchmarking dependencies
uv pip install -e ".[dev,docs,bench]"

# Alternative (using pip)
python -m venv venv && source venv/bin/activate && pip install -e ".[dev,docs]"
```
License: MIT | Python: >=3.10

### Testing
```bash
# Run all tests
pytest

# Run with coverage
pytest --cov=pyisolate --cov-report=html --cov-report=term-missing
## Architecture

# Run integration tests
pytest tests/test_integration.py -v
### Public API (`pyisolate/`)

| File | Purpose |
|------|---------|
| `__init__.py` | Package exports: `ExtensionBase`, `ExtensionManager`, `ExtensionConfig`, `SandboxMode`, `ProxiedSingleton`, `SealedNodeExtension`, adapter registration |
| `host.py` | `ExtensionManager` — creates/manages isolated extensions and their venvs |
| `shared.py` | `ExtensionBase` / `ExtensionLocal` — base classes with lifecycle hooks (`before_module_loaded`, `on_module_loaded`) |
| `sealed.py` | `SealedNodeExtension` — minimal extension for sealed workers (no host framework imports) |
| `config.py` | TypedDicts: `ExtensionManagerConfig`, `ExtensionConfig`, `SandboxConfig`, `CUDAWheelConfig`. Enum: `SandboxMode` |
| `interfaces.py` | `IsolationAdapter` and `SerializerRegistryProtocol` — structural typing protocols for application adapters |
| `path_helpers.py` | Host `sys.path` serialization and child-side reconstruction |

### Internal (`pyisolate/_internal/`)

| File | Purpose |
|------|---------|
| `rpc_protocol.py` | `AsyncRPC` engine, `ProxiedSingleton` metaclass, `LocalMethodRegistry` |
| `rpc_transports.py` | `JSONSocketTransport` (primary) — length-prefixed JSON over UDS. No pickle. `QueueTransport` (legacy) |
| `rpc_serialization.py` | Message structures: `RPCRequest`, `RPCResponse`, `RPCCallback` |
| `serialization_registry.py` | `SerializerRegistry` — O(1) type lookup, MRO chain, `data_type` flag |
| `tensor_serializer.py` | Zero-copy tensors via `/dev/shm` (CPU) and CUDA IPC (GPU). `TensorKeeper` (5.0s default retention) |
| `model_serialization.py` | Generic `serialize_for_isolation()` / `deserialize_from_isolation()` |
| `host.py` | `Extension` class — process lifecycle (venv → deps → launch → RPC → shutdown) |
| `bootstrap.py` | Child-side init: sys.path reconstruction, adapter rehydration |
| `uds_client.py` | Child entrypoint (`python -m pyisolate._internal.uds_client`) |
| `environment.py` | uv venv creation, dependency installation, torch package exclusion |
| `environment_conda.py` | pixi/conda environment creation, fingerprint caching |
| `cuda_wheels.py` | CUDA wheel resolver — probes host torch/CUDA, fetches matching wheels |
| `sandbox.py` | `build_bwrap_command()` — deny-by-default filesystem, GPU passthrough, sealed-worker `--clearenv` |
| `sandbox_detect.py` | Multi-distro detection: RHEL, Ubuntu AppArmor, SELinux, Arch |
| `event_bridge.py` | Child-to-host event dispatch |
| `perf_trace.py` | Structured event logging (`PYISOLATE_TRACE_FILE`) |

### Other Directories

| Directory | Purpose |
|-----------|---------|
| `tests/` | Unit and integration tests (pytest, ~50 test functions) |
| `example/` | Working 3-extension demo showing dependency isolation (numpy 1.x vs 2.x) |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded test count will drift, to wit!

The "~50 test functions" is a hardcoded count that will become stale as tests are added or removed. Consider whether this number adds value or if it should be omitted to reduce maintenance burden.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 51, The README contains a hardcoded "~50 test functions"
count that will become stale; edit CLAUDE.md to remove or generalize that phrase
(e.g., replace "~50 test functions" with "multiple test functions", "dozens of
tests", or omit the count entirely) so the description remains accurate without
requiring updates when tests change; search for the literal "~50 test functions"
in CLAUDE.md and update the sentence accordingly.

| `benchmarks/` | RPC overhead and memory benchmarks with cross-platform runner scripts |
| `docs/` | Reference docs: RPC protocol, debugging, edge cases, platform compatibility |

# Test the working example
cd example && python main.py -v
```
## Development Commands

### Benchmarking
```bash
# Install benchmark dependencies
uv pip install -e ".[bench]"

# Run full benchmark suite
python benchmark.py

# Quick benchmarks (fewer iterations)
python benchmark.py --quick

# Skip torch benchmarks
python benchmark.py --no-torch
# Environment setup
uv venv && source .venv/bin/activate && uv pip install -e ".[dev,test]"
pre-commit install

# Run benchmarks via pytest
pytest tests/test_benchmarks.py -v -s
```
# Testing
pytest # all tests with coverage
pytest tests/test_rpc_contract.py -v # specific test file
pytest -k "test_sandbox" -v # pattern match

### Code Quality
```bash
# Lint and format
# Code quality
ruff check pyisolate tests
ruff format pyisolate tests

# All quality checks
tox -e lint
```

### Build
```bash
# Build package
# Build
python -m build

# Build docs
cd docs && make html
```

## Architecture

### Core Components
- **ExtensionManager**: Manages multiple extensions and their isolated venvs
- **ExtensionBase**: Base class for extensions with lifecycle hooks (`before_module_loaded`, `on_module_loaded`)
- **AsyncRPC**: Inter-process communication system with context-aware call tracking
- **ProxiedSingleton**: Enables shared APIs across processes (like `DatabaseSingleton` in example)

Note: The example uses a two-level pattern where `ExampleExtensionBase` handles the lifecycle hooks and creates actual extension instances that implement `initialize()`, `prepare_shutdown()`, and custom methods like `do_stuff()`.
## Public API Surface

### Key Directories
- `pyisolate/`: Main package with public API in `__init__.py`
- `pyisolate/_internal/`: Core implementation (RPC, process management)
- `example/`: Working demo with 3 extensions showcasing dependency conflicts
- `tests/`: Integration and edge case tests
Exported from `pyisolate/__init__.py`:

### Extension Workflow
1. ExtensionManager creates isolated venv per extension
2. Installs extension-specific dependencies
3. Launches extension in separate process via `_internal/client.py`
4. Establishes bidirectional RPC communication
5. Extensions can call host methods and shared singletons transparently
```text
ExtensionBase ExtensionManager ExtensionManagerConfig
ExtensionConfig SandboxMode SealedNodeExtension
ProxiedSingleton local_execution singleton_scope
flush_tensor_keeper purge_orphan_sender_shm_files
register_adapter get_adapter
```

## Configuration
Everything in `_internal/` is private implementation. Do not expose internal types in public API changes.

### Python Support
3.9 - 3.12 (tested in CI)
## Isolation Modes

### Dependencies
- **Runtime**: None (pure Python)
- **Development**: pytest, ruff, pre-commit
- **Testing**: torch>=2.0.0, numpy (for `share_torch` and tensor tests)
- **Benchmarking**: torch, numpy, psutil, tabulate (for performance measurement)
| Mode | Provisioner | share_torch | Tensor Transport |
|:-----|:------------|:------------|:-----------------|
| cuda_share | uv | yes | CUDA IPC + `/dev/shm` |
| torch_share | uv | yes | `/dev/shm` only |
| json_share | uv | no | JSON serialization |
| sealed_worker | uv or pixi | no | JSON serialization |

### Key Config Files
- `pyproject.toml`: Project metadata, dependencies, tool configuration
- `tox.ini`: Multi-Python testing environments
- `.pre-commit-config.yaml`: Git hooks for code quality
Invalid: pixi + `share_torch=True`. Invalid: `share_cuda_ipc=True` without `share_torch=True`.

## Special Features
## Testing Conventions

### Dependency Isolation
Each extension gets its own venv - handles conflicting packages like numpy 1.x vs 2.x (demonstrated in example).
- Tests live in `tests/` with `test_` prefix
- Integration tests that create real venvs are slow (~30s each)
- Tests marked `@pytest.mark.network` require external network access
- `pytest --cov=pyisolate` for coverage (configured in pyproject.toml)
- No tests should import from `_internal/` unless testing internal behavior

### PyTorch Sharing
Use `share_torch: true` in extension config to share PyTorch models across processes for memory efficiency.
## Environment Variables

### RPC Patterns
- Extensions can call host methods recursively (host→extension→host)
- Shared singletons work transparently via RPC proxying
- Context tracking prevents circular calls
| Variable | Purpose |
|----------|---------|
| `PYISOLATE_CHILD` | `"1"` in child processes |
| `PYISOLATE_DEBUG_RPC` | `"1"` for verbose RPC logging |
| `PYISOLATE_TRACE_FILE` | Path for structured perf trace JSONL output |
| `PYISOLATE_ENABLE_CUDA_IPC` | `"1"` to enable CUDA IPC tensor transport |
| `PYISOLATE_PATH_DEBUG` | `"1"` for sys.path logging during child init |
| `PYISOLATE_ENFORCE_SANDBOX` | Force bwrap sandboxing |
Comment on lines 109 to +117
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for environment variable references in the codebase

for var in PYISOLATE_CHILD PYISOLATE_DEBUG_RPC PYISOLATE_TRACE_FILE PYISOLATE_ENABLE_CUDA_IPC PYISOLATE_PATH_DEBUG PYISOLATE_ENFORCE_SANDBOX; do
  echo "=== Checking $var ==="
  rg -n "$var" pyisolate/ --type py | head -5
done

# Also search for any PYISOLATE_ variables not in the list
echo "=== Searching for undocumented PYISOLATE_ variables ==="
rg -n 'PYISOLATE_[A-Z_]+' pyisolate/ --type py -o | sort -u

Repository: Comfy-Org/pyisolate

Length of output: 4908


Update the environment variables table with missing variables and remove unused entries.

The documented variables are incomplete and contain inaccuracies. Two variables (PYISOLATE_PATH_DEBUG and PYISOLATE_ENFORCE_SANDBOX) are documented but not actually used in the codebase, while 11+ environment variables are actively referenced but missing from the table. This misdirects developers—it's not an airtight documentation if there are so many loose ends!

Add these actively-used variables to the table:

  • PYISOLATE_HOST_SNAPSHOT — Host Python snapshot for initialization
  • PYISOLATE_MODULE_PATH — Module search paths for child process
  • PYISOLATE_UV_CACHE_DIR — UV package manager cache directory
  • PYISOLATE_UDS_ADDRESS — Unix domain socket address for IPC
  • PYISOLATE_EXTENSION — Extension modules to load in child
  • PYISOLATE_IMPORT_TORCH — Control torch import behavior
  • PYISOLATE_FORCE_CUDA_IPC — Force CUDA IPC (distinct from enable flag)
  • PYISOLATE_PURGE_SENDER_SHM — Shared memory cleanup control
  • PYISOLATE_SIGNAL_CLEANUP — Signal-based cleanup flag
  • PYISOLATE_CPU_BORROWED_SHM — CPU tensor shared memory management
  • PYISOLATE_CPU_TENSOR_FORCE_CLONE_ON_DESERIALIZE — CPU tensor deserialization behavior

Remove PYISOLATE_PATH_DEBUG and PYISOLATE_ENFORCE_SANDBOX if they are not production features or move them to a separate "legacy/experimental" section if applicable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 109 - 117, Update the environment variables table to
reflect the actual, actively-used variables: add entries for
PYISOLATE_HOST_SNAPSHOT, PYISOLATE_MODULE_PATH, PYISOLATE_UV_CACHE_DIR,
PYISOLATE_UDS_ADDRESS, PYISOLATE_EXTENSION, PYISOLATE_IMPORT_TORCH,
PYISOLATE_FORCE_CUDA_IPC, PYISOLATE_PURGE_SENDER_SHM, PYISOLATE_SIGNAL_CLEANUP,
PYISOLATE_CPU_BORROWED_SHM, and PYISOLATE_CPU_TENSOR_FORCE_CLONE_ON_DESERIALIZE
with concise purpose descriptions matching their usage in the codebase (e.g.,
host snapshot, module search paths, UV cache dir, UDS address, extension
loading, torch import control, CUDA IPC forcing, SHM purge control, signal
cleanup, CPU SHM management, and CPU tensor clone-on-deserialize behavior);
remove PYISOLATE_PATH_DEBUG and PYISOLATE_ENFORCE_SANDBOX from the main table
(or move them to a clearly labeled legacy/experimental section) so the
documentation matches actual implementation.


## Testing Notes
## Key Invariants

The test suite covers real venv creation, dependency conflicts, and RPC edge cases. The `example/` directory provides a working demonstration with 3 extensions that showcase the core functionality.
- No pickle anywhere in the transport layer — JSON-RPC only
- Library is application-agnostic — no references to specific integrations in library code
- Fail loud — surface failures immediately, no silent degradation
- `_internal/` is private — public API goes through `__init__.py` exports
Loading
Loading