Files

5.3 KiB

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:

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:

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:

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".
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,
        )