-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: implement remote execution environment backends (SSH, Modal, Daytona) #1400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
c450836
feat: implement remote execution environment backends (SSH, Modal, Da…
praisonai-triage-agent[bot] 4937912
fix: critical security and functional issues in sandbox backends
praisonai-triage-agent[bot] 92ce12a
fix sandbox backends review issues: security hardening and test relia…
Copilot 1c3055b
fix sandbox backend hardening and stabilize remote backend tests
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,301 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Remote Sandbox Examples for PraisonAI | ||
|
|
||
| Demonstrates how to use SSH, Modal, and Daytona sandbox backends | ||
| for remote code execution. | ||
| """ | ||
|
|
||
| import asyncio | ||
| import os | ||
| from typing import Optional | ||
|
|
||
| from praisonai.sandbox import SSHSandbox, ModalSandbox, DaytonaSandbox | ||
| from praisonaiagents.sandbox import ResourceLimits | ||
|
|
||
|
|
||
| async def ssh_example(): | ||
| """Example using SSH sandbox for remote execution.""" | ||
| print("🔗 SSH Sandbox Example") | ||
| print("=" * 50) | ||
|
|
||
| # Configure SSH connection | ||
| # Note: You need actual SSH server access for this to work | ||
| ssh_host = os.getenv("SSH_HOST", "localhost") | ||
| ssh_user = os.getenv("SSH_USER", "ubuntu") | ||
| ssh_key = os.getenv("SSH_KEY_FILE", "~/.ssh/id_rsa") | ||
|
|
||
| print(f"Connecting to {ssh_user}@{ssh_host}") | ||
|
|
||
| try: | ||
| sandbox = SSHSandbox( | ||
| host=ssh_host, | ||
| user=ssh_user, | ||
| key_file=ssh_key, | ||
| working_dir="/tmp/praisonai_demo" | ||
| ) | ||
|
|
||
| # Check availability | ||
| if not sandbox.is_available: | ||
| print("❌ SSH backend not available. Install with: pip install praisonai[ssh]") | ||
| return | ||
|
|
||
| # Start sandbox | ||
| await sandbox.start() | ||
| print("✅ SSH connection established") | ||
|
|
||
| # Execute Python code | ||
| print("\n📄 Executing Python code...") | ||
| result = await sandbox.execute(""" | ||
| import sys | ||
| import platform | ||
| print(f"Python version: {sys.version}") | ||
| print(f"Platform: {platform.platform()}") | ||
| print("Hello from remote SSH server! 🚀") | ||
| """, language="python") | ||
|
|
||
| print(f"Exit code: {result.exit_code}") | ||
| print(f"Output:\n{result.stdout}") | ||
| if result.stderr: | ||
| print(f"Errors:\n{result.stderr}") | ||
|
|
||
| # Execute shell command | ||
| print("\n🐚 Executing shell command...") | ||
| result = await sandbox.run_command("uname -a && uptime") | ||
| print(f"System info:\n{result.stdout}") | ||
|
|
||
| # Write and read file | ||
| print("\n📝 File operations...") | ||
| await sandbox.write_file("/tmp/praisonai_demo/hello.py", "print('Hello from file!')") | ||
| content = await sandbox.read_file("/tmp/praisonai_demo/hello.py") | ||
| print(f"File content: {content}") | ||
|
|
||
| # Execute the file | ||
| result = await sandbox.execute_file("/tmp/praisonai_demo/hello.py") | ||
| print(f"File execution result: {result.stdout}") | ||
|
|
||
| # Test resource limits | ||
| print("\n⏱️ Testing resource limits...") | ||
| limits = ResourceLimits(timeout_seconds=5, memory_mb=128) | ||
| result = await sandbox.execute(""" | ||
| import time | ||
| print("Starting long task...") | ||
| time.sleep(10) # This will timeout | ||
| print("Task completed") | ||
| """, limits=limits) | ||
|
|
||
| print(f"Limited execution status: {result.status}") | ||
| if result.error: | ||
| print(f"Error (expected timeout): {result.error}") | ||
|
|
||
| await sandbox.stop() | ||
| print("✅ SSH sandbox stopped") | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ SSH example failed: {e}") | ||
|
|
||
|
|
||
| async def modal_example(): | ||
| """Example using Modal sandbox for serverless GPU execution.""" | ||
| print("\n☁️ Modal Sandbox Example") | ||
| print("=" * 50) | ||
|
|
||
| try: | ||
| # Note: Requires Modal account and API key | ||
| sandbox = ModalSandbox( | ||
| gpu="A100", # Request A100 GPU | ||
| timeout=120 | ||
| ) | ||
|
|
||
| # Check availability | ||
| if not sandbox.is_available: | ||
| print("❌ Modal backend not available. Install with: pip install praisonai[modal]") | ||
| return | ||
|
|
||
| print("🚀 Starting Modal app...") | ||
| await sandbox.start() | ||
| print("✅ Modal app initialized") | ||
|
|
||
| # Execute GPU-accelerated Python code | ||
| print("\n🔥 Executing GPU code...") | ||
| result = await sandbox.execute(""" | ||
| import torch | ||
| import numpy as np | ||
|
|
||
| print(f"PyTorch version: {torch.__version__}") | ||
| print(f"CUDA available: {torch.cuda.is_available()}") | ||
|
|
||
| if torch.cuda.is_available(): | ||
| device = torch.cuda.get_device_name() | ||
| print(f"GPU device: {device}") | ||
|
|
||
| # Simple tensor operations on GPU | ||
| x = torch.randn(1000, 1000, device='cuda') | ||
| y = torch.randn(1000, 1000, device='cuda') | ||
| z = torch.matmul(x, y) | ||
| print(f"GPU computation result shape: {z.shape}") | ||
| else: | ||
| print("Running on CPU") | ||
| x = torch.randn(100, 100) | ||
| y = torch.randn(100, 100) | ||
| z = torch.matmul(x, y) | ||
| print(f"CPU computation result shape: {z.shape}") | ||
|
|
||
| print("🚀 Modal execution completed!") | ||
| """, language="python") | ||
|
|
||
| print(f"Exit code: {result.exit_code}") | ||
| print(f"Output:\n{result.stdout}") | ||
| if result.stderr: | ||
| print(f"Errors:\n{result.stderr}") | ||
|
|
||
| # Test different languages | ||
| print("\n🐚 Testing bash execution...") | ||
| result = await sandbox.run_command("nvidia-smi || echo 'No GPU info available'") | ||
| print(f"GPU info:\n{result.stdout}") | ||
|
|
||
| await sandbox.stop() | ||
| print("✅ Modal sandbox stopped") | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Modal example failed: {e}") | ||
|
|
||
|
|
||
| async def daytona_example(): | ||
| """Example using Daytona sandbox for cloud dev environment.""" | ||
| print("\n🌤️ Daytona Sandbox Example") | ||
| print("=" * 50) | ||
|
|
||
| try: | ||
| # Configure Daytona workspace | ||
| sandbox = DaytonaSandbox( | ||
| workspace_template="python-dev", | ||
| provider="aws", # or "gcp", "azure", "local" | ||
| timeout=180 | ||
| ) | ||
|
|
||
| # Check availability | ||
| if not sandbox.is_available: | ||
| print("❌ Daytona backend not available. Install with: pip install praisonai[daytona]") | ||
| return | ||
|
|
||
| print(f"🏗️ Creating Daytona workspace: {sandbox.workspace_name}") | ||
| await sandbox.start() | ||
| print("✅ Daytona workspace ready") | ||
|
|
||
| # Execute development tasks | ||
| print("\n💻 Running development tasks...") | ||
| result = await sandbox.execute(""" | ||
| import sys | ||
| import os | ||
| import subprocess | ||
|
|
||
| print("🐍 Python Development Environment") | ||
| print(f"Python version: {sys.version}") | ||
| print(f"Working directory: {os.getcwd()}") | ||
|
|
||
| # Check installed packages | ||
| try: | ||
| result = subprocess.run([sys.executable, "-m", "pip", "list"], | ||
| capture_output=True, text=True) | ||
| print("📦 Installed packages:") | ||
| print(result.stdout[:500] + "..." if len(result.stdout) > 500 else result.stdout) | ||
| except Exception as e: | ||
| print(f"Could not list packages: {e}") | ||
|
|
||
| print("✨ Daytona workspace is ready for development!") | ||
| """, language="python") | ||
|
|
||
| print(f"Exit code: {result.exit_code}") | ||
| print(f"Output:\n{result.stdout}") | ||
|
|
||
| # Test file operations | ||
| print("\n📁 Workspace file operations...") | ||
| await sandbox.write_file("/workspace/demo.py", """ | ||
| def hello_daytona(): | ||
| print("Hello from Daytona workspace!") | ||
| return "🎉 Success!" | ||
|
|
||
| if __name__ == "__main__": | ||
| result = hello_daytona() | ||
| print(result) | ||
| """) | ||
|
|
||
| # Execute the file | ||
| result = await sandbox.execute_file("/workspace/demo.py") | ||
| print(f"Demo script output: {result.stdout}") | ||
|
|
||
| # List workspace files | ||
| files = await sandbox.list_files("/workspace") | ||
| print(f"Workspace files: {files}") | ||
|
|
||
| await sandbox.stop() | ||
| print("✅ Daytona workspace stopped") | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Daytona example failed: {e}") | ||
|
|
||
|
|
||
| async def comparison_example(): | ||
| """Compare execution across different sandbox backends.""" | ||
| print("\n⚖️ Sandbox Comparison") | ||
| print("=" * 50) | ||
|
|
||
| # Simple test code | ||
| test_code = """ | ||
| import time | ||
| import sys | ||
| start_time = time.time() | ||
| print(f"Hello from {sys.platform}!") | ||
| print(f"Execution time: {time.time() - start_time:.3f}s") | ||
| """ | ||
|
|
||
| # Test each sandbox type | ||
| sandboxes = [ | ||
| ("SSH", SSHSandbox(host=os.getenv("SSH_HOST", "localhost"))), | ||
| ("Modal", ModalSandbox()), | ||
| ("Daytona", DaytonaSandbox()) | ||
| ] | ||
|
|
||
| for name, sandbox in sandboxes: | ||
| print(f"\n🧪 Testing {name} sandbox...") | ||
|
|
||
| if not sandbox.is_available: | ||
| print(f"❌ {name} not available") | ||
| continue | ||
|
|
||
| try: | ||
| await sandbox.start() | ||
| result = await sandbox.execute(test_code, language="python") | ||
|
|
||
| print(f"✅ {name}: {result.status}") | ||
| print(f" Duration: {result.duration_seconds:.3f}s") | ||
| print(f" Output: {result.stdout.strip()}") | ||
|
|
||
| await sandbox.stop() | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ {name} failed: {e}") | ||
|
|
||
|
|
||
| async def main(): | ||
| """Run all examples.""" | ||
| print("🚀 PraisonAI Remote Sandbox Examples") | ||
| print("=" * 70) | ||
|
|
||
| # Run examples | ||
| await ssh_example() | ||
| await modal_example() | ||
| await daytona_example() | ||
| await comparison_example() | ||
|
|
||
| print("\n✅ All examples completed!") | ||
| print("\nNext steps:") | ||
| print("1. Set up SSH access to remote servers") | ||
| print("2. Configure Modal account for GPU workloads") | ||
| print("3. Set up Daytona for cloud development") | ||
| print("4. Use these sandboxes in your PraisonAI agents!") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean up remote resources in a
finallyblock.If anything raises after
start(), these examples leak SSH sessions, Modal apps, or Daytona workspaces. Please stop the sandbox on the error path too.Also applies to: 103-161, 169-236, 267-278
🧰 Tools
🪛 Ruff (0.15.10)
[error] 35-35: Probable insecure usage of temporary file or directory: "/tmp/praisonai_demo"
(S108)
[error] 69-69: Probable insecure usage of temporary file or directory: "/tmp/praisonai_demo/hello.py"
(S108)
[error] 70-70: Probable insecure usage of temporary file or directory: "/tmp/praisonai_demo/hello.py"
(S108)
[error] 74-74: Probable insecure usage of temporary file or directory: "/tmp/praisonai_demo/hello.py"
(S108)
[warning] 94-94: Do not catch blind exception:
Exception(BLE001)
🤖 Prompt for AI Agents