Files
cleveragents-core/docs/reference/devcontainer_resources.md
T
HAL9000 18d00c04c4
CI / lint (pull_request) Failing after 1m15s
CI / quality (pull_request) Successful in 1m21s
CI / typecheck (pull_request) Successful in 1m34s
CI / security (pull_request) Successful in 1m37s
CI / coverage (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 1m37s
CI / docker (pull_request) Has been skipped
CI / build (pull_request) Successful in 33s
CI / helm (pull_request) Successful in 26s
CI / push-validation (pull_request) Successful in 19s
CI / e2e_tests (pull_request) Successful in 3m20s
CI / integration_tests (pull_request) Successful in 4m32s
CI / status-check (pull_request) Failing after 3s
fix(skills): implement multi-scope agent skill discovery for global, project, and local tiers
Implements AgentSkillDiscovery class to support discovering Agent Skills from
multiple configured directories across three scopes (global, project, local).
Handles name collisions with precedence: local > project > global.

Adds comprehensive BDD test coverage for multi-scope discovery scenarios including:
- Global-only, project-only, and local-only discovery
- Combined discovery from all scopes
- Name collision resolution with proper precedence
- Non-existent and empty scope directory handling
- Multiple skills in same scope discovery

ISSUES CLOSED: #9369
2026-05-06 19:55:22 +00:00

312 lines
13 KiB
Markdown

# Devcontainer Resources
Devcontainer resources integrate the
[Development Containers Specification](https://containers.dev) into the
CleverAgents resource model. Two resource types and an auto-discovery
mechanism allow CleverAgents to detect, register, and use devcontainer
environments automatically.
## Resource Types
### `devcontainer-instance`
A container execution environment defined by a
`.devcontainer/devcontainer.json` configuration file. Inherits from
`container-instance` (see ADR-042 for resource type inheritance).
| Property | Value |
|-------------------|-----------------------|
| Kind | physical |
| Sandbox strategy | none (container provides isolation) |
| User-addable | yes |
| Parent types | git-checkout, fs-directory |
| Child types | devcontainer-file |
| Handler | `DevcontainerHandler` |
**CLI arguments:**
- `--path` (path, required): Path to the directory containing
`.devcontainer/devcontainer.json`.
### `devcontainer-file`
A single-file resource pointing to the `devcontainer.json`
configuration file itself. Created automatically as a child of
`devcontainer-instance` during auto-discovery.
| Property | Value |
|-------------------|-----------------------|
| Kind | physical |
| Sandbox strategy | copy\_on\_write |
| User-addable | no |
| Parent types | devcontainer-instance |
| Child types | (none) |
| Handler | `DevcontainerHandler` |
### `container-instance`
The base container resource type from which `devcontainer-instance`
inherits. Represents a generic container execution environment.
| Property | Value |
|-------------------|-----------------------|
| Kind | physical |
| Sandbox strategy | none (container provides isolation) |
| User-addable | yes |
| Parent types | git-checkout, fs-directory |
| Child types | (none) |
| Handler | `DevcontainerHandler` |
## Auto-Discovery (Planned)
> **Not yet wired (F31/F23):** The discovery module
> (`discover_devcontainers()`) exists and is tested in isolation, but
> is **not** invoked during `project link-resource` or any other
> production code path. Auto-discovery will be wired in a follow-up PR.
> For now, devcontainer-instance resources must be added manually via
> `agents resource add`.
When wired, linking a `git-checkout` or `fs-directory` resource to a
project will trigger an auto-discovery hook that scans for devcontainer
configurations in the following locations (relative to the resource
root):
1. `.devcontainer/devcontainer.json`
2. `.devcontainer.json` (root-level)
### Discovery Process (Planned)
1. **Trigger**: Linking a `git-checkout` or `fs-directory` resource.
2. **Scan**: The discovery module checks for configuration files at the
well-known paths listed above.
3. **Validate**: Each discovered file is parsed as JSON. Invalid files
are skipped with a warning.
4. **Register**: For each valid configuration:
- A `devcontainer-instance` child resource is created under the
parent resource.
- A `devcontainer-file` child resource is created under the
devcontainer instance, pointing to the JSON file.
### Lazy Activation
Devcontainers use lazy activation: the container is only built and
started when first needed by a plan execution. The discovery phase
only registers the resource metadata.
## Lifecycle Management
Devcontainer-instance resources support a full lifecycle state
machine with six states and validated transitions.
### State Diagram
```
detected ──► building ──► running ──► stopping ──► stopped
│ │
▼ │
failed ◄───────────────────────────────┘
│ ▲
└──► building │
(retry) (rebuild)
stopped ────────┘
```
### States
| State | Description |
|------------|---------------------------------------------------|
| `detected` | Container not yet started (default for new) |
| `building` | Container is being activated (`devcontainer up`) |
| `running` | Container is running and healthy |
| `stopping` | Container is being stopped |
| `stopped` | Container has been stopped cleanly |
| `failed` | Container is in an error state |
### Valid Transitions
| From | To | Trigger |
|-----------|---------------------|-----------------------|
| detected | building | lazy activation |
| building | running | `devcontainer up` OK |
| building | failed | `devcontainer up` fail|
| running | stopping | stop command |
| stopping | stopped | container stopped |
| stopping | failed | stop failed |
| stopped | building | rebuild |
| failed | building | retry |
### Lazy Activation Flow
When a tool targets a devcontainer-instance that is in `detected`
state:
1. Transition to `building`.
2. Run `devcontainer up --workspace-folder <location>`.
3. Parse JSON output for `containerId` and `remoteWorkspaceFolder`.
4. On success: transition to `running`.
5. On failure: transition to `failed`.
### Health Checking
> **Implementation-ahead-of-spec (SPEC-3):** Health checking and
> session-scoped cleanup are implemented but not yet formally specified
> in `docs/specification.md` for container resources (the spec only
> defines health checking for LSP servers). These features fulfil
> Issue #514 acceptance criteria and will be added to the specification
> in a follow-up PR.
Active containers receive periodic health probes. Health checking
starts automatically when a container transitions to `running` state
via `activate_container`.
- **Command**: `devcontainer exec ... echo ping`
- **Default interval**: 30 seconds (configurable via
`health_check_interval` on the lifecycle tracker).
- **On failure**: transitions to `failed` via `stopping`.
- Health checks run as background daemon threads.
- Health checks are stopped automatically before container stop.
### Session/Plan Cleanup
When a session closes or a plan completes/fails/is-cancelled, active
devcontainer-instances **scoped to that session or plan** are stopped
automatically. Each `ContainerLifecycleTracker` carries an optional
`session_id` field, set during activation, used to scope cleanup.
Cleanup is wired into the following hooks:
- `A2aLocalFacade._handle_session_close` — session close
- `PlanLifecycleService.complete_apply` — plan success
- `PlanLifecycleService.fail_apply` — plan apply failure
- `PlanLifecycleService.fail_execute` — plan execute failure
- `PlanLifecycleService.cancel_plan` — plan cancellation
```python
CleanupService.stop_active_devcontainers(session_id="...")
```
## CLI Usage
```bash
# Manually add a devcontainer instance
agents resource add devcontainer-instance local/my-dc --path /home/user/project
# Add a container instance
agents resource add container-instance local/my-ctr --image ubuntu:latest
# List all devcontainer resources
agents resource list --type devcontainer-instance
# Show devcontainer details
agents resource show local/my-dc
# View resource tree (shows devcontainer children)
agents resource tree local/my-repo
# Inspect resource with tree view
agents resource inspect local/my-repo --tree
# Stop an active devcontainer
agents resource stop local/my-dc
# Stop without confirmation prompt (for scripts)
agents resource stop local/my-dc --yes
# Rebuild a stopped or errored devcontainer
agents resource rebuild local/my-dc --yes
```
### Stop Command
```bash
agents resource stop <devcontainer-name> [--yes|-y]
```
Transitions an active container through `running → stopping → stopped`.
Only `devcontainer-instance` resources may be stopped (F19 fix:
`container-instance` was removed because it has no lifecycle tracker
wiring). Prompts for confirmation unless `--yes` (`-y`) is passed.
If the container is already in a terminal state (`stopped` or
`failed`), the stop is a no-op and returns immediately.
### Rebuild Command
```bash
agents resource rebuild <devcontainer-name> [--yes|-y]
```
Transitions a stopped or errored container through
`stopped/failed → building → running`. Runs `devcontainer up` to
rebuild the container. Only `devcontainer-instance` resources may
be rebuilt (not generic `container-instance`). Prompts for
confirmation unless `--yes` (`-y`) is passed.
## Known Limitations
| Limitation | Detail | Planned Fix |
|------------|--------|-------------|
| In-memory lifecycle state (F20) | Lifecycle state is tracked in an in-memory registry (`_lifecycle_registry` dict), **not** persisted on the resource model or database. State is lost on process restart. CLI `stop`/`rebuild` in a new process cannot see trackers from a previous process. | M7+: Persist `activation_state` on the resource model per the spec field definition, hydrating trackers on CLI operations. |
| Registry eviction | Terminal-state trackers (stopped/failed) are evicted when count exceeds 200 (`_MAX_TERMINAL_TRACKERS`). Eviction runs automatically after bulk cleanup via `stop_all_active_containers`. Long-running processes with many container cycles may lose historical tracker data. | Acceptable for MVP; persisted state in M7+ eliminates this. |
| Sandbox strategy (F22/F25) | Specification uses `container_snapshot`; `SandboxFactory` does not yet implement `snapshot`. Handler now uses `SandboxStrategy.NONE` — the container itself provides isolation. | Implement `container_snapshot` in `SandboxFactory` and switch handler back to it. |
| Hardcoded `docker stop` (F21) | `stop_container()` calls `docker stop` directly. Devcontainer CLI can target Podman or other engines; the devcontainer CLI does not yet offer a `stop` subcommand. | Detect the container engine from resource properties or devcontainer CLI config and dispatch to the appropriate stop command. |
| Auto-discovery not wired (F23) | Documentation describes auto-discovery on `project link-resource`, but no code path invokes `discover_devcontainers()` during resource linking. | Wire the discovery hook into the resource linking flow. |
| Container execution stubbed (F24) | `ToolRunner` returns an error for `ExecutionEnvironment.CONTAINER`, so lazy activation cannot trigger via tool use in production. Lazy activation currently works through `DevcontainerHandler.resolve()` on plan sandbox resolution. | Full container execution support is scoped for a follow-up PR (#616). |
## DevcontainerHandler Protocol Methods
The following protocol methods were completed in v3.7.0 (issue #1242) as
part of the ResourceHandler Protocol Completion epic (#825). All methods
raise `ValueError` if the resource has no location.
| Method | Behaviour |
|--------|-----------|
| `delete(*, resource, path="")` | Runs `devcontainer exec rm -rf <path>` inside the container. Returns `DeleteResult(success=False)` for missing or stopped containers rather than raising. Empty `path` targets the workspace root. |
| `list_children(*, resource)` | Runs `devcontainer exec ls -1` to enumerate workspace entries. Returns an empty list on container failure. |
| `diff(*, resource, other_location)` | Compares content hashes between the devcontainer workspace and another filesystem location. Uses `EMPTY_CONTENT_HASH` sentinel to treat both-absent as no-change. Returns `DiffResult`. |
| `create_sandbox(*, resource)` | Delegates to `BaseResourceHandler.create_sandbox` with lazy activation (same `DETECTED`/`STOPPED`/`FAILED` guard as `resolve()`). |
### Error Handling
All four methods follow the same pattern:
- If the resource has no location → raise `ValueError`.
- If the container is missing, stopped, or the subprocess fails → return a
failure result (not raise an exception).
This ensures that tool execution against a stopped devcontainer produces a
clear failure message rather than an unhandled exception.
---
## Architecture
The devcontainer integration follows ADR-043 and builds on the
resource type inheritance model defined in ADR-042.
### Handler
`DevcontainerHandler` extends `BaseResourceHandler` and uses the
`none` sandbox strategy (the container itself provides isolation).
For `devcontainer-instance` resources, the handler overrides
`resolve()` to trigger lazy activation via `activate_container()`
when the resource is in a non-running state. It handles both
`devcontainer-instance` and `devcontainer-file` resource types.
### Discovery Module
The `cleveragents.resource.handlers.discovery` module provides:
- `discover_devcontainers(location, type)`: Scans a path for
devcontainer configs.
- `is_trigger_type(type)`: Checks if a resource type triggers
auto-discovery.
- `DevcontainerDiscoveryResult`: Data class holding discovery results.
## See Also
- [ADR-042: Resource Type Inheritance](../adr/ADR-042-resource-type-inheritance.md)
- [ADR-043: Devcontainer Integration](../adr/ADR-043-devcontainer-integration.md)
- [Resource Types (Built-in)](resource_types_builtin.md)
- [Resource Handlers](resource_handlers.md)