173 lines
5.3 KiB
Markdown
173 lines
5.3 KiB
Markdown
# 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,
|
|
)
|
|
```
|