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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ jobs:
run: |
uv run coverage run -m pytest
uv run coverage report
uv run coverage lcov -o coverage.lcov

- name: Upload coverage
- name: Upload coverage to Coveralls
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v4
uses: coverallsapp/github-action@v2
with:
name: coverage-report
path: .coverage/
file: coverage.lcov

docs:
name: Build Documentation
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.12] - 2025-01-16

### Changed

- Updated `pydantic-ai-backend` dependency to `>=0.0.4` for persistent storage support
- `__version__` now dynamically reads from package metadata (pyproject.toml) via `importlib.metadata`

### Documentation

- Added persistent storage documentation to `docs/examples/docker-sandbox.md`:
- `volumes` parameter for DockerSandbox
- `workspace_root` parameter for SessionManager
- Added `workspace_root` documentation to `docs/examples/docker-runtimes.md`:
- Configuration options section
- New "Persistent Storage with workspace_root" section with examples
- Directory structure diagram
- Updated `docs/examples/full-app.md` with `workspace_root` in SessionManager example
- Updated `examples/full_app/app.py` to use `workspace_root` for persistent user files
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![PyPI version](https://img.shields.io/pypi/v/pydantic-deep.svg)](https://pypi.org/project/pydantic-deep/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/vstorm-co/pydantic-deep)
[![Coverage Status](https://coveralls.io/repos/github/vstorm-co/pydantic-deepagents/badge.svg?branch=main)](https://coveralls.io/github/vstorm-co/pydantic-deepagents?branch=main)
[![CI](https://github.com/vstorm-co/pydantic-deep/actions/workflows/ci.yml/badge.svg)](https://github.com/vstorm-co/pydantic-deep/actions/workflows/ci.yml)

Deep agent framework built on [pydantic-ai](https://github.com/pydantic/pydantic-ai) with planning, filesystem, and subagent capabilities.
Expand Down
39 changes: 39 additions & 0 deletions docs/examples/docker-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,47 @@ await manager.shutdown()
SessionManager(
default_runtime="python-datascience", # Default for new sessions
default_idle_timeout=3600, # 1 hour idle timeout
workspace_root="/var/app/workspaces", # Persistent storage directory
)
```

### Persistent Storage with workspace_root

By default, files in Docker containers are lost when the container stops. Use `workspace_root` to automatically persist files for each session:

```python
manager = SessionManager(
default_runtime="python-datascience",
workspace_root="/var/app/workspaces", # Base directory
)

# For user-123, creates: /var/app/workspaces/user-123/workspace/
# Mounted as /workspace in container
sandbox = await manager.get_or_create("user-123")

# Files persist even after container stops
await sandbox.write("/workspace/report.pdf", pdf_content)
await manager.shutdown()

# Later, when user returns...
sandbox = await manager.get_or_create("user-123")
content = await sandbox.read("/workspace/report.pdf") # Still there!
```

!!! tip "Directory Structure"
```
/var/app/workspaces/
├── user-123/
│ └── workspace/ → mounted as /workspace
│ ├── report.pdf
│ └── data.csv
├── user-456/
│ └── workspace/
│ └── analysis.py
└── user-789/
└── workspace/
```

## Complete Example

```python
Expand All @@ -181,6 +219,7 @@ async def main():
manager = SessionManager(
default_runtime=runtime,
default_idle_timeout=1800, # 30 minutes
workspace_root="./workspaces", # Persist user files
)

try:
Expand Down
66 changes: 65 additions & 1 deletion docs/examples/docker-sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,74 @@ sandbox = DockerSandbox(
sandbox = DockerSandbox(
image="python:3.12",
work_dir="/app",
# Additional options can be added to the implementation
auto_remove=True, # Remove container on stop
idle_timeout=3600, # Container lifetime in seconds
)
```

### Persistent Storage with Volumes

By default, files inside the Docker container are lost when the container stops. Use `volumes` to persist files on the host filesystem:

```python
sandbox = DockerSandbox(
image="python:3.12-slim",
volumes={
"/path/on/host": "/workspace", # host_path: container_path
},
)

# Files written to /workspace persist on /path/on/host
sandbox.write("/workspace/data.json", '{"key": "value"}')
sandbox.stop()

# Later, files are still there when container restarts
sandbox = DockerSandbox(
image="python:3.12-slim",
volumes={"/path/on/host": "/workspace"},
)
content = sandbox.read("/workspace/data.json") # '{"key": "value"}'
```

Multiple volume mappings are supported:

```python
sandbox = DockerSandbox(
image="python:3.12-slim",
volumes={
"/host/workspace": "/workspace",
"/host/data": "/data",
"/host/config": "/config",
},
)
```

### Automatic Persistent Storage with SessionManager

For multi-user applications, `SessionManager` provides automatic per-session persistent storage:

```python
from pydantic_deep import SessionManager

# Create manager with workspace_root
manager = SessionManager(
workspace_root="/var/app/workspaces", # Base directory for all sessions
)

# Each session gets its own persistent directory
sandbox = manager.get_or_create("user-123")
# Creates: /var/app/workspaces/user-123/workspace/
# Mounted as: /workspace in container

# User returns later - files still there
sandbox2 = manager.get_or_create("user-123")
content = sandbox2.read("/workspace/previous_file.py") # Still exists!
```

!!! tip "When to Use Each Approach"
- **`volumes`**: Direct control over mount points, custom paths
- **`workspace_root`**: Automatic per-session directories, multi-user apps

## Execution

The `execute` tool runs commands inside the container:
Expand Down
3 changes: 3 additions & 0 deletions docs/examples/full-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ async def lifespan(app: FastAPI):
session_manager = SessionManager(
default_runtime=None,
default_idle_timeout=3600,
workspace_root="./workspaces", # Persistent storage for user files
)
session_manager.start_cleanup_loop(interval=300)

Expand All @@ -161,6 +162,8 @@ async def lifespan(app: FastAPI):
await session_manager.shutdown()
```

With `workspace_root`, each session gets persistent storage at `./workspaces/{session_id}/workspace/`, so user files survive container restarts and app reboots.

### WebSocket Streaming

```python
Expand Down
6 changes: 5 additions & 1 deletion examples/full_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@
# Paths
APP_DIR = Path(__file__).parent
WORKSPACE_DIR = APP_DIR / "workspace"
WORKSPACES_DIR = APP_DIR / "workspaces" # Per-session persistent storage
SKILLS_DIR = APP_DIR / "skills"
STATIC_DIR = APP_DIR / "static"

# Create workspace if it doesn't exist
# Create directories if they don't exist
WORKSPACE_DIR.mkdir(exist_ok=True)
WORKSPACES_DIR.mkdir(exist_ok=True)


@dataclass
Expand Down Expand Up @@ -266,11 +268,13 @@ async def lifespan(app: FastAPI):
session_manager = SessionManager(
default_runtime=None, # Will use default python:3.12-slim
default_idle_timeout=3600, # 1 hour idle timeout
workspace_root=WORKSPACES_DIR, # Persistent storage for user files
)
session_manager.start_cleanup_loop(interval=300) # Cleanup every 5 min

print("Agent initialized (shared across sessions)")
print(f"Skills directory: {SKILLS_DIR}")
print(f"Persistent workspaces: {WORKSPACES_DIR}")
print("Session manager started with auto-cleanup")
yield

Expand Down
7 changes: 6 additions & 1 deletion pydantic_deep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ class Analysis(BaseModel):
UploadedFile,
)

__version__ = "0.1.0"
try:
from importlib.metadata import version

__version__ = version("pydantic-deep")
except Exception: # pragma: no cover
__version__ = "0.0.0"

__all__ = [
# Main entry points
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "pydantic-deep"
version = "0.2.11"
version = "0.2.12"
description = "Deep Agent framework built on Pydantic-ai with planning, filesystem, and subagent capabilities"
readme = "README.md"
license = "MIT"
Expand All @@ -25,14 +25,14 @@ classifiers = [
dependencies = [
"pydantic-ai-slim>=0.1.0",
"pydantic-ai-todo>=0.1.0",
"pydantic-ai-backend>=0.0.3",
"pydantic-ai-backend>=0.0.4",
"pydantic>=2.0",
"chardet>=5.2.0",
]

[project.optional-dependencies]
# Docker sandbox support
sandbox = ["pydantic-ai-backend[docker]>=0.0.3"]
sandbox = ["pydantic-ai-backend[docker]>=0.0.4"]
# CLI tools (for interactive chat examples)
cli = [
"typer>=0.12.0",
Expand Down