Run Claude Agent SDK agents in Modal sandboxes
Project description
Modal Agents SDK
Disclaimer: This is an unofficial community package. It is not affiliated with, endorsed by, or associated with Anthropic or Modal in any way.
Run Claude Agent SDK agents in Modal sandboxes.
This package wraps the Claude Agent SDK to execute AI agents in secure, scalable Modal containers. It provides progressive complexity—simple usage mirrors the original Agent SDK, while advanced features expose Modal's full capabilities (GPU, volumes, image customization, etc.).
Features
| Feature | modal-agents-sdk | claude-agent-sdk |
|---|---|---|
| Sandboxed execution | ✅ Modal containers | ❌ Local only |
| GPU support | ✅ A10G, H100, A100, etc. | ❌ |
| Persistent storage | ✅ Modal Volumes | ❌ |
| Custom images | ✅ Docker/Dockerfile | ❌ |
| Network isolation | ✅ Configurable | ❌ |
| Auto-scaling | ✅ Built-in | ❌ |
| Built-in tools | ✅ Read, Write, Bash, etc. | ✅ |
| MCP servers | ✅ | ✅ |
| Host-side hooks | ✅ Intercept tool calls | ❌ |
| Host-side tools | ✅ Run on local machine | ❌ |
| Multi-turn conversations | ✅ | ✅ |
Installation
pip install modal-agents-sdk
Prerequisites
- Modal account: Sign up at modal.com
- Modal CLI: Install and authenticate
pip install modal modal setup
- Anthropic API key: Create a Modal secret
modal secret create anthropic-key ANTHROPIC_API_KEY=sk-ant-...
Quick Start
import asyncio
from modal_agents_sdk import query
async def main():
async for message in query("What is 2 + 2?"):
print(message)
asyncio.run(main())
Basic Usage: query()
query() is an async function for querying Claude in a Modal sandbox. It returns an AsyncIterator of response messages.
from modal_agents_sdk import query, ModalAgentOptions, AssistantMessage, TextBlock
import modal
# Simple query
async for message in query(prompt="Hello Claude"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
# With options
options = ModalAgentOptions(
system_prompt="You are a helpful assistant",
max_turns=3,
secrets=[modal.Secret.from_name("anthropic-key")],
)
async for message in query(prompt="Tell me a joke", options=options):
print(message)
Using Tools
options = ModalAgentOptions(
allowed_tools=["Read", "Write", "Bash"],
permission_mode="acceptEdits", # auto-accept file edits
secrets=[modal.Secret.from_name("anthropic-key")],
)
async for message in query(prompt="Create a hello.py file", options=options):
pass
Working Directory
from pathlib import Path
options = ModalAgentOptions(
cwd="/workspace/myproject", # or Path("/workspace/myproject")
secrets=[modal.Secret.from_name("anthropic-key")],
)
GPU Compute
options = ModalAgentOptions(
gpu="A10G", # or "H100", "A100-80GB:2", etc.
memory=16384, # 16 GB
secrets=[modal.Secret.from_name("anthropic-key")],
)
Persistent Storage
import modal
data_volume = modal.Volume.from_name("my-data", create_if_missing=True)
options = ModalAgentOptions(
volumes={"/data": data_volume},
secrets=[modal.Secret.from_name("anthropic-key")],
)
# Files written to /data persist across sandbox executions
Custom Image
from modal_agents_sdk import ModalAgentImage
image = (
ModalAgentImage.default()
.pip_install("pandas", "numpy", "scikit-learn")
.apt_install("ffmpeg")
.run_commands("npm install -g typescript")
)
options = ModalAgentOptions(
image=image,
secrets=[modal.Secret.from_name("anthropic-key")],
)
Network Restrictions
The agent requires network access to call the Anthropic API. Use cidr_allowlist to restrict access while allowing the API:
# Anthropic API CIDR (required): 160.79.104.0/23
# Source: https://docs.anthropic.com/en/api/ip-addresses
options = ModalAgentOptions(
cidr_allowlist=["160.79.104.0/23"], # Anthropic API only
secrets=[modal.Secret.from_name("anthropic-key")],
)
Note: block_network=True is not supported as it would prevent API calls.
ModalAgentClient
ModalAgentClient supports multi-turn conversations:
from modal_agents_sdk import ModalAgentClient, ModalAgentOptions
import modal
options = ModalAgentOptions(
secrets=[modal.Secret.from_name("anthropic-key")],
)
async with ModalAgentClient(options=options) as client:
await client.query("Create a Python project structure")
async for msg in client.receive_response():
print(msg)
# Follow-up (maintains context)
await client.query("Now add a requirements.txt")
async for msg in client.receive_response():
print(msg)
MCP Servers
options = ModalAgentOptions(
mcp_servers={
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"],
},
},
secrets=[modal.Secret.from_name("anthropic-key")],
)
Host-Side Hooks
Intercept and control tool calls from your local machine while the agent runs in the sandbox:
from modal_agents_sdk import (
ModalAgentHooks,
PreToolUseHookInput,
PreToolUseHookResult,
ModalAgentOptions,
)
async def block_dangerous_commands(input: PreToolUseHookInput) -> PreToolUseHookResult:
"""Block dangerous bash commands before execution."""
if input.tool_name == "Bash" and "rm -rf" in input.tool_input.get("command", ""):
return PreToolUseHookResult(
decision="deny",
reason="Blocked dangerous command",
)
return PreToolUseHookResult(decision="allow")
hooks = ModalAgentHooks(
pre_tool_use=[block_dangerous_commands],
tool_filter="Bash|Write|Edit", # Only intercept these tools
)
options = ModalAgentOptions(
host_hooks=hooks,
secrets=[modal.Secret.from_name("anthropic-key")],
)
Host-Side Tools
Define custom tools that run on your local machine but can be called by the agent in the sandbox:
from modal_agents_sdk import host_tool, HostToolServer, ModalAgentOptions
import os
@host_tool(
name="get_secret",
description="Retrieve a secret from local environment",
input_schema={"key": str},
)
async def get_secret(args):
"""Access local environment variables not available in sandbox."""
value = os.environ.get(args["key"], "")
return {"content": [{"type": "text", "text": f"Secret value: {value}"}]}
server = HostToolServer(name="local-tools", tools=[get_secret])
options = ModalAgentOptions(
host_tools=[server],
secrets=[modal.Secret.from_name("anthropic-key")],
)
# Agent can now call get_secret to access your local environment
async for message in query("Get the DATABASE_URL secret", options=options):
print(message)
Types
See src/modal_agents_sdk/_types.py for complete type definitions. Key types are re-exported from claude-agent-sdk:
AssistantMessage,UserMessage,SystemMessage,ResultMessage- Message typesTextBlock,ToolUseBlock,ToolResultBlock,ThinkingBlock- Content blocks
Error Handling
from modal_agents_sdk import (
ModalAgentError, # Base error
SandboxCreationError, # Failed to create sandbox
SandboxTimeoutError, # Execution timed out
SandboxTerminatedError, # Sandbox terminated
ImageBuildError, # Image build failed
CLINotInstalledError, # claude-agent-sdk not in image
AgentExecutionError, # Agent execution failed
)
try:
async for message in query(prompt="Hello", options=options):
pass
except SandboxTimeoutError:
print("Execution timed out")
except AgentExecutionError as e:
print(f"Agent failed: exit code {e.exit_code}")
Examples
See the examples/ directory for complete working examples:
Getting Started
quick_start.py- Basic usage with message type handlingmulti_turn.py- Multi-turn conversations withModalAgentClient
Infrastructure & Resources
custom_image.py- Custom container images with pip/apt packagesgpu_compute.py- GPU-enabled agents (A10G, CUDA, PyTorch)resource_limits.py- CPU, memory, and timeout configurationcloud_region.py- Cloud provider and region selection (AWS, GCP)
Storage & Persistence
persistent_storage.py- Using Modal volumes for data persistencenetwork_file_system.py- NFS for shared storage across sandboxesephemeral_volume_upload.py- Upload local files to sandboxsandbox_snapshot.py- Save and restore sandbox filesystem statesession_resume.py- Persist conversation state across runs
Security & Monitoring
security_sandbox.py- Network isolation with CIDR allowlisthooks.py- Host-side hooks for security, monitoring, and tool interceptionbudget_control.py- Cost tracking and budget limits
Advanced Features
model_selection.py- Choose Claude models (Haiku, Sonnet, Opus)extended_thinking.py- Complex reasoning with visible thought processstructured_output.py- JSON responses with defined schemasmulti_agent.py- Define specialized sub-agents for delegationprogrammatic_subagents.py- Custom agents withAgentDefinitionhost_tools.py- Custom tools that run on host machine
Integrations
tunnel_web_app.py- Build and expose web servers via encrypted tunnels
Development
git clone https://github.com/sshh12/modal-claude-agent-sdk-python
cd modal-claude-agent-sdk-python
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
# Run checks manually
pytest # Run tests
mypy src/ # Type checking
ruff check src/ # Linting
ruff format src/ tests/ # Format code
License
MIT License - see LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file modal_agents_sdk-0.1.3.tar.gz.
File metadata
- Download URL: modal_agents_sdk-0.1.3.tar.gz
- Upload date:
- Size: 69.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94b844fc8757f86849114ce893c34f0b5fd9801a59de8f7fae1fe35286c69a5b
|
|
| MD5 |
a13973b6c8397193c97f3d974ee36b1b
|
|
| BLAKE2b-256 |
64f0b2d2ab1d479e1935f26f6c97e6975ddceffb1371e8f86a2571732c1d9772
|
Provenance
The following attestation bundles were made for modal_agents_sdk-0.1.3.tar.gz:
Publisher:
publish.yml on sshh12/modal-claude-agent-sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
modal_agents_sdk-0.1.3.tar.gz -
Subject digest:
94b844fc8757f86849114ce893c34f0b5fd9801a59de8f7fae1fe35286c69a5b - Sigstore transparency entry: 834270109
- Sigstore integration time:
-
Permalink:
sshh12/modal-claude-agent-sdk-python@777735dd06887a751925b3c8082c7cc8e7256498 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sshh12
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@777735dd06887a751925b3c8082c7cc8e7256498 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file modal_agents_sdk-0.1.3-py3-none-any.whl.
File metadata
- Download URL: modal_agents_sdk-0.1.3-py3-none-any.whl
- Upload date:
- Size: 35.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9975e752f9de078d9abc46aae394f4946e6d25b285e1cb3988c5a5bab27af7b
|
|
| MD5 |
1e7ae56a4275d948658e539c8ca97ecd
|
|
| BLAKE2b-256 |
13bd43fb971bd031305fb3792075954e2200db8503468935e996536cd45fba44
|
Provenance
The following attestation bundles were made for modal_agents_sdk-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on sshh12/modal-claude-agent-sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
modal_agents_sdk-0.1.3-py3-none-any.whl -
Subject digest:
a9975e752f9de078d9abc46aae394f4946e6d25b285e1cb3988c5a5bab27af7b - Sigstore transparency entry: 834270110
- Sigstore integration time:
-
Permalink:
sshh12/modal-claude-agent-sdk-python@777735dd06887a751925b3c8082c7cc8e7256498 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sshh12
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@777735dd06887a751925b3c8082c7cc8e7256498 -
Trigger Event:
workflow_dispatch
-
Statement type: