# Resource Handlers Resource handlers bridge resource types to sandbox provisioning. When a plan executes, each tool's resource slot is resolved to a physical sandbox path through the handler pipeline. ## Architecture ``` BindingResolutionService ResourceHandlerService resolve(tool, project) resolve_binding(binding, plan_id) │ │ ▼ ▼ BindingResult(resource_id) ┌── Resource (location) │ ResourceTypeSpec (handler, strategy) │ │ │ ▼ │ resolve_handler("module:Class") │ │ │ ▼ │ handler.resolve(resource, sandbox_manager) │ │ │ ▼ └── BoundResource(sandbox_path="/tmp/sandbox/...") ``` ## Handler Protocol All handlers implement the `ResourceHandler` protocol: ```python class ResourceHandler(Protocol): def resolve( self, *, resource: Resource, plan_id: str, slot_name: str, sandbox_manager: SandboxManager, access: str = "read_only", ) -> BoundResource: ... ``` ## Built-in Handlers ### GitCheckoutHandler Resolves `git-checkout` resources using the `git_worktree` sandbox strategy. | Property | Value | |----------|-------| | Module | `cleveragents.resource.handlers.git_checkout` | | Class | `GitCheckoutHandler` | | Default strategy | `git_worktree` | | Fallback strategy | `copy_on_write` | | Required fields | `resource.location` (path to git repo root) | ### FsDirectoryHandler Resolves `fs-directory` resources using the `copy_on_write` sandbox strategy. | Property | Value | |----------|-------| | Module | `cleveragents.resource.handlers.fs_directory` | | Class | `FsDirectoryHandler` | | Default strategy | `copy_on_write` | | Required fields | `resource.location` (path to directory) | ## Handler Resolution Handler strings use the `module.path:ClassName` format and are stored on `ResourceTypeSpec.handler`. Resolution is dynamic via `importlib`: ```python from cleveragents.resource.handlers import resolve_handler handler = resolve_handler( "cleveragents.resource.handlers.git_checkout:GitCheckoutHandler" ) ``` Resolved handlers are cached for the process lifetime. Call `clear_handler_cache()` to reset. ### Fallback Behavior If no handler string is set (or resolution fails), the `ResourceHandlerService` uses a default handler that delegates directly to `SandboxManager` using the type's `sandbox_strategy`. ## Strategy Precedence The sandbox strategy is determined in this order: 1. **Resource-level override** (`resource.sandbox_strategy`) — per-resource 2. **Type default** (`ResourceTypeSpec.sandbox_strategy`) — per-type 3. **Fallback** (`none`) — no sandboxing ## ResourceHandlerService The orchestration service that chains the resolution pipeline: ```python from cleveragents.application.services.resource_handler_service import ( ResourceHandlerService, ) service = ResourceHandlerService( sandbox_manager=sandbox_manager, resource_lookup=registry_service.show_resource, type_lookup=registry_service.show_type, ) # Resolve a single binding bound = service.resolve_binding(binding, plan_id="01ARZ3...") # Resolve all bindings for a tool bindings_map = service.resolve_bindings(bindings, plan_id="01ARZ3...") # -> {"repo": BoundResource(sandbox_path="/tmp/sandbox/...")} ``` ## Sandbox Outputs After resolution, each `BoundResource` carries: | Field | Description | |-------|-------------| | `slot_name` | Tool slot this binding fills | | `resource_id` | ULID of the resolved resource | | `resource_type` | Type name (e.g. `git-checkout`) | | `sandbox_path` | Root path of the provisioned sandbox | | `access` | `read_only` or `read_write` | The `sandbox_path` points to an isolated directory managed by `SandboxManager`. Changes are committed or rolled back via `SandboxManager.commit_all()` / `rollback_all()`. ## Writing Custom Handlers To add a handler for a new resource type: 1. Create a module under `cleveragents/resource/handlers/`. 2. Implement a class satisfying `ResourceHandler`. 3. Register it on the `ResourceTypeSpec` via the `handler` field: `"cleveragents.resource.handlers.my_handler:MyHandler"`. ```python class MyHandler: def resolve( self, *, resource: Resource, plan_id: str, slot_name: str, sandbox_manager: SandboxManager, access: str = "read_only", ) -> BoundResource: # Custom validation / setup sandbox = sandbox_manager.get_or_create_sandbox( plan_id=plan_id, resource_id=resource.resource_id, original_path=resource.location, sandbox_strategy="copy_on_write", ) return BoundResource( slot_name=slot_name, resource_id=resource.resource_id, resource_type=resource.resource_type_name, sandbox_path=sandbox.context.sandbox_path if sandbox.context else "", access=access, ) ```