Skip to content

Sessions Guide

Sessions provide stateful command execution in AgentFense sandboxes. This guide explains what sessions are, why they're essential, and how to use them effectively.


What is a Session?

A Session is a persistent shell process running inside a sandbox that preserves state across multiple command executions.

Without Session (Direct Execution)

sandbox.run("cd /workspace/backend")  # Shell process #1
sandbox.run("source venv/bin/activate")  # Shell process #2 (fresh start)
sandbox.run("python manage.py test")  # Shell process #3 (fresh start)
# ❌ Each command runs in a NEW shell - no state preserved

Result: The cd and source commands have no effect on subsequent commands because each run() starts a fresh shell.


With Session (Stateful Execution)

with sandbox.session() as session:
    session.exec("cd /workspace/backend")  # Shell process persists
    session.exec("source venv/bin/activate")  # Same shell, virtualenv activated
    session.exec("python manage.py test")  # Same shell, still in backend/ with venv
# ✓ All commands run in the SAME shell - state persists

Result: Each command builds on the previous state. The final python command runs in /workspace/backend/ with the virtualenv activated.


Why Use Sessions?

Sessions are required when you need to:

  1. Change working directory (cd)

    with sandbox.session() as session:
        session.exec("cd /workspace/src")
        session.exec("pytest")  # Runs in /workspace/src
    

  2. Set environment variables (export)

    with sandbox.session() as session:
        session.exec("export DEBUG=1")
        session.exec("python app.py")  # DEBUG=1 is set
    

  3. Activate virtual environments (source)

    with sandbox.session() as session:
        session.exec("source venv/bin/activate")
        session.exec("pip list")  # Shows venv packages
    

  4. Run background processes

    with sandbox.session() as session:
        session.exec("redis-server &")  # Starts background server
        session.exec("python test.py")  # Can connect to redis
    

  5. Multi-step workflows with dependencies

    with sandbox.session() as session:
        session.exec("npm install")  # Install dependencies
        session.exec("npm run build")  # Uses installed deps
        session.exec("npm test")  # Uses built artifacts
    


Creating Sessions

Sync API

from agentfense import Sandbox

with Sandbox.from_local("./project") as sandbox:
    with sandbox.session() as session:
        result = session.exec("pwd")
        print(result.stdout)  # /workspace

Async API

from agentfense import AsyncSandbox

async with await AsyncSandbox.from_local("./project") as sandbox:
    async with await sandbox.session() as session:
        result = await session.exec("pwd")
        print(result.stdout)  # /workspace

Session Parameters

def session(
    self,
    shell: str = "/bin/sh",
    env: Optional[Dict[str, str]] = None,
) -> SessionWrapper
Parameter Type Default Description
shell str "/bin/sh" Shell binary to use (/bin/sh, /bin/bash, /bin/zsh, etc.)
env Dict[str, str] None Initial environment variables

Example: Custom Shell

with sandbox.session(shell="/bin/bash") as session:
    # Bash-specific syntax (arrays, etc.)
    session.exec("declare -a arr=(1 2 3)")

Example: Initial Environment

with sandbox.session(env={"DEBUG": "1", "ENV": "test"}) as session:
    result = session.exec("printenv DEBUG")
    print(result.stdout)  # 1


Session State Management

Working Directory

The current working directory (cwd) persists across commands:

with sandbox.session() as session:
    session.exec("cd /workspace/backend")
    result = session.exec("pwd")
    print(result.stdout)  # /workspace/backend

    session.exec("cd ../frontend")
    result = session.exec("pwd")
    print(result.stdout)  # /workspace/frontend

Environment Variables

Environment variables set with export persist:

with sandbox.session() as session:
    session.exec("export API_URL=http://localhost:8000")
    session.exec("export DB_NAME=testdb")

    result = session.exec("printenv API_URL")
    print(result.stdout)  # http://localhost:8000

Aliases and Functions

Shell aliases and functions are preserved:

with sandbox.session(shell="/bin/bash") as session:
    session.exec("alias ll='ls -la'")
    result = session.exec("ll")
    print(result.stdout)  # Runs ls -la

    session.exec("myfunction() { echo 'Hello from function'; }")
    result = session.exec("myfunction")
    print(result.stdout)  # Hello from function

Background Processes

Background processes (&) continue running:

with sandbox.session() as session:
    # Start a background server
    session.exec("python -m http.server 8000 &")

    # Wait for server to start
    import time
    time.sleep(2)

    # Server is still running
    result = session.exec("curl http://localhost:8000")
    print(result.stdout)  # HTML directory listing

Important: Background processes are killed when the session closes.


Common Use Cases

Use Case 1: Python Virtual Environment

with sandbox.session() as session:
    # Create virtualenv
    session.exec("python -m venv venv")

    # Activate it
    session.exec("source venv/bin/activate")

    # Install dependencies
    session.exec("pip install pytest requests")

    # Run tests (using venv's pytest)
    result = session.exec("pytest tests/")
    print(result.exit_code)

Use Case 2: Node.js Project Setup

with sandbox.session() as session:
    # Install dependencies
    session.exec("cd /workspace")
    session.exec("npm install")

    # Run linter
    lint_result = session.exec("npm run lint")

    # Run tests
    test_result = session.exec("npm test")

    # Build production bundle
    if test_result.exit_code == 0:
        session.exec("npm run build")

Use Case 3: Database Setup for Tests

with sandbox.session() as session:
    # Start database in background
    session.exec("mysqld --datadir=/tmp/mysql &")

    # Wait for startup
    import time
    time.sleep(3)

    # Run migrations
    session.exec("python manage.py migrate")

    # Run tests
    result = session.exec("python manage.py test")

Use Case 4: Multi-Stage CI Pipeline

def ci_pipeline(sandbox):
    """Run full CI pipeline in one session."""
    with sandbox.session() as session:
        # Stage 1: Setup
        session.exec("cd /workspace")
        session.exec("export CI=true")

        # Stage 2: Install
        result = session.exec("pip install -r requirements.txt")
        if result.exit_code != 0:
            return False, "Install failed"

        # Stage 3: Lint
        result = session.exec("flake8 .")
        if result.exit_code != 0:
            return False, "Linting failed"

        # Stage 4: Test
        result = session.exec("pytest --cov")
        if result.exit_code != 0:
            return False, "Tests failed"

        # Stage 5: Build
        result = session.exec("python setup.py bdist_wheel")
        if result.exit_code != 0:
            return False, "Build failed"

        return True, "Pipeline succeeded"

# Usage
with Sandbox.from_local("./project") as sandbox:
    success, message = ci_pipeline(sandbox)
    print(message)

Use Case 5: Interactive Debugging

with sandbox.session() as session:
    # Set up environment
    session.exec("cd /workspace")
    session.exec("source .env")

    # Run debugger commands
    session.exec("python -m pdb main.py")
    session.exec("break main.py:42")
    session.exec("continue")

    # Inspect variables
    result = session.exec("print(local_vars)")
    print(result.stdout)

Session vs Direct Execution

When to Use Direct Execution (run() / exec())

One-off commands: Single independent commands
No state needed: Command doesn't depend on previous state
Maximum isolation: Each command starts fresh
Simplicity: No need to manage session lifecycle

Example:

# These are independent - no session needed
sandbox.run("python --version")
sandbox.run("ls -la")
sandbox.run("cat README.md")


When to Use Sessions (session())

Working directory changes: Need cd to persist
Environment setup: Need export, source, alias
Virtualenv/tools: Activating Python venv, Node.js NVM, etc.
Background services: Start server, run tests against it
Multi-step workflows: Each step builds on previous

Example:

# These depend on each other - session required
with sandbox.session() as session:
    session.exec("cd backend/")
    session.exec("source venv/bin/activate")
    session.exec("pytest")


Performance Comparison

Aspect Direct Execution Session
Startup overhead ~10-50ms per command ~50ms once, then ~5-20ms per command
State isolation Complete (new shell each time) Shared (same shell)
Best for Independent commands Dependent workflows

Recommendation: Use sessions for 3+ dependent commands. For 1-2 independent commands, use direct execution.


Best Practices

1. Always Use Context Manager

# ✓ Good: Auto cleanup
with sandbox.session() as session:
    session.exec("command")
# Session automatically closed

# ✗ Bad: Manual cleanup (easy to forget)
session = sandbox.session()
session.exec("command")
session.close()  # Easy to forget

2. Check Exit Codes

with sandbox.session() as session:
    result = session.exec("make build")
    if result.exit_code != 0:
        print(f"Build failed: {result.stderr}")
        return  # Exit early on failure

    # Only continue if build succeeded
    session.exec("make test")

3. Set Environment Early

# ✓ Good: Set env at session creation
with sandbox.session(env={"CI": "1", "DEBUG": "0"}) as session:
    session.exec("pytest")

# ✗ Less efficient: Set env per command
with sandbox.session() as session:
    session.exec("export CI=1")  # Extra command
    session.exec("pytest")

4. Use Absolute Paths When Possible

# ✓ Good: Absolute path (unambiguous)
with sandbox.session() as session:
    session.exec("python /workspace/scripts/test.py")

# ✗ Risky: Relative path (depends on cwd)
with sandbox.session() as session:
    session.exec("python scripts/test.py")  # Fails if cwd != /workspace

5. Limit Session Scope

# ✓ Good: One session per logical workflow
with sandbox.session() as session:
    # Backend tests
    session.exec("cd /workspace/backend")
    session.exec("pytest")

with sandbox.session() as session:
    # Frontend tests (fresh session)
    session.exec("cd /workspace/frontend")
    session.exec("npm test")

# ✗ Bad: Reuse session for unrelated workflows
with sandbox.session() as session:
    session.exec("cd /workspace/backend")
    session.exec("pytest")
    session.exec("cd /workspace/frontend")  # Risk of cross-contamination
    session.exec("npm test")

Troubleshooting

Problem: cd Doesn't Persist

Symptom:

sandbox.run("cd /workspace/src")
result = sandbox.run("pwd")
print(result.stdout)  # /workspace (not /workspace/src)

Solution: Use a session:

with sandbox.session() as session:
    session.exec("cd /workspace/src")
    result = session.exec("pwd")
    print(result.stdout)  # /workspace/src


Problem: Environment Variable Not Set

Symptom:

sandbox.run("export DEBUG=1")
result = sandbox.run("printenv DEBUG")
print(result.stdout)  # (empty)

Solution: Use a session or pass env:

# Option 1: Session
with sandbox.session() as session:
    session.exec("export DEBUG=1")
    result = session.exec("printenv DEBUG")
    print(result.stdout)  # 1

# Option 2: Pass env parameter
result = sandbox.run("printenv DEBUG", env={"DEBUG": "1"})
print(result.stdout)  # 1


Problem: Background Process Terminates

Symptom:

with sandbox.session() as session:
    session.exec("redis-server &")
# Session closed, redis-server killed

Solution: Keep session open as long as you need the background process:

with sandbox.session() as session:
    session.exec("redis-server &")
    time.sleep(2)  # Wait for startup

    # Use the server while session is open
    result = session.exec("redis-cli ping")
    print(result.stdout)  # PONG
# Now session closes and kills background processes


Problem: Session Hangs on Interactive Command

Symptom:

with sandbox.session() as session:
    session.exec("python")  # Hangs waiting for input

Solution: Avoid interactive commands or use stdin:

# Option 1: Non-interactive flag
session.exec("python -c 'print(1+1)'")

# Option 2: Use stdin (not supported in sessions, use direct exec)
result = sandbox.exec("python", stdin="print(1+1)\nexit()\n")


See Also