Files
cleveragents-core/docs/development/custom_sandbox_strategy.md
T
freemo 2688c85769
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 19s
CI / lint (pull_request) Successful in 23s
CI / quality (pull_request) Successful in 28s
CI / security (pull_request) Successful in 54s
CI / typecheck (pull_request) Successful in 1m25s
CI / e2e_tests (pull_request) Successful in 2m33s
CI / unit_tests (pull_request) Successful in 3m4s
CI / integration_tests (pull_request) Successful in 3m33s
CI / docker (pull_request) Successful in 1m7s
CI / coverage (pull_request) Successful in 5m50s
CI / lint (push) Successful in 18s
CI / build (push) Successful in 33s
CI / quality (push) Successful in 47s
CI / typecheck (push) Successful in 49s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 52s
CI / e2e_tests (push) Successful in 2m9s
CI / unit_tests (push) Successful in 3m32s
CI / integration_tests (push) Successful in 3m41s
CI / docker (push) Successful in 56s
CI / coverage (push) Successful in 6m10s
CI / benchmark-publish (push) Has been cancelled
CI / benchmark-regression (pull_request) Successful in 40m21s
feat(extensibility): implement Custom Sandbox Strategy Registration via SandboxStrategy Protocol
Implement SandboxStrategyProtocol, a 9-method @runtime_checkable Protocol enabling
third-party sandbox strategy registration. Includes:

- SandboxStrategyProtocol with create/read/write/diff/commit/rollback/checkpoint/
  restore_checkpoint/cleanup methods
- SandboxRef (frozen dataclass) and DiffView/DiffEntry (Pydantic models)
- SandboxStrategyRegistry with config-driven registration, Protocol validation,
  thread safety, and clear/list/has operations
- BuiltInSandboxStrategyAdapter wrapping existing Sandbox implementations to conform
  to the new Protocol
- CustomStrategyConfig for YAML/dict-based strategy registration
- SandboxFactory integration with custom_registry parameter,
  has_custom_strategy() and get_custom_strategy_class()
- 25 Behave BDD scenarios (85 steps) covering protocol, registry, adapter, config,
  and factory integration
- 8 Robot Framework integration tests with real filesystem operations
- ASV benchmarks for registry and adapter operations
- Developer documentation

ISSUES CLOSED: #586
2026-03-16 22:50:27 +00:00

4.0 KiB

Custom Sandbox Strategy Registration

Overview

CleverAgents supports custom sandbox strategies via the SandboxStrategyProtocol. Custom strategies enable domain-specific isolation for specialized resource types that are not covered by the built-in strategies (none, git_worktree, copy_on_write, transaction_rollback).

SandboxStrategy Protocol

All custom sandbox strategies must implement the 9-method SandboxStrategyProtocol:

from cleveragents.domain.models.core.sandbox_strategy import (
    SandboxStrategyProtocol,
    SandboxRef,
    DiffEntry,
    DiffView,
)
from cleveragents.domain.models.core.resource import Resource


class MyCustomSandbox:
    def create(self, plan_id: str, resource: Resource) -> SandboxRef: ...
    def read(self, ref: SandboxRef, path: str) -> bytes: ...
    def write(self, ref: SandboxRef, path: str, content: bytes) -> DiffEntry: ...
    def diff(self, ref: SandboxRef) -> DiffView: ...
    def commit(self, ref: SandboxRef) -> None: ...
    def rollback(self, ref: SandboxRef) -> None: ...
    def checkpoint(self, ref: SandboxRef, checkpoint_id: str) -> None: ...
    def restore_checkpoint(self, ref: SandboxRef, checkpoint_id: str) -> None: ...
    def cleanup(self, ref: SandboxRef) -> None: ...

Supporting Models

SandboxRef

Opaque reference returned by create() and passed to all other methods:

@dataclass(frozen=True)
class SandboxRef:
    sandbox_id: str
    plan_id: str
    resource_id: str
    created_at: datetime
    metadata: dict[str, object] = field(default_factory=dict)

DiffEntry / DiffView

DiffEntry describes a single file change; DiffView aggregates entries:

class DiffEntry(BaseModel):
    path: str          # relative file path
    operation: str     # "added", "modified", or "deleted"
    before: bytes | None
    after: bytes | None

class DiffView(BaseModel):
    sandbox_id: str
    entries: list[DiffEntry]
    summary: str

Config-Driven Registration

Custom strategies are registered via the SandboxStrategyRegistry:

from cleveragents.infrastructure.sandbox.strategy_registry import (
    SandboxStrategyRegistry,
)

registry = SandboxStrategyRegistry(
    allowed_prefixes=("cleveragents.", "mycompany."),
)

# Single registration
registry.register(
    "my_strategy",
    "mycompany.sandbox.custom",
    "MyCustomSandbox",
)

# Batch config-driven registration
configs = {
    "redis_sandbox": {
        "module": "mycompany.sandbox.redis",
        "class": "RedisSandbox",
    },
    "s3_sandbox": {
        "module": "mycompany.sandbox.s3",
        "class": "S3Sandbox",
    },
}
registry.register_all_from_config(configs)

Factory Integration

The SandboxFactory can be initialized with a custom registry:

from cleveragents.infrastructure.sandbox.factory import SandboxFactory

factory = SandboxFactory(custom_registry=registry)

# Check if custom strategy is available
if factory.has_custom_strategy("redis_sandbox"):
    cls = factory.get_custom_strategy_class("redis_sandbox")

Built-In Strategy Adapter

The BuiltInSandboxStrategyAdapter wraps existing Sandbox implementations to conform to the SandboxStrategyProtocol:

from cleveragents.infrastructure.sandbox.strategy_adapter import (
    BuiltInSandboxStrategyAdapter,
)

adapter = BuiltInSandboxStrategyAdapter(strategy_name="copy_on_write")
ref = adapter.create("plan-001", resource)
adapter.write(ref, "file.txt", b"content")
data = adapter.read(ref, "file.txt")
adapter.cleanup(ref)

Protocol Validation

All strategies are validated against the Protocol at registration time. The registry checks that all 9 required methods are present as callable attributes:

# This will raise ProtocolMismatchError if the class
# doesn't implement all required methods:
registry.register("bad", "mymodule", "IncompleteClass")

Security

Module imports are restricted to a configurable prefix allowlist (default: ("cleveragents.",)). Only modules whose fully-qualified name starts with an allowed prefix may be dynamically imported.