# 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 Auto-discovery is wired into `GitCheckoutHandler.discover_children()` and `FsDirectoryHandler.discover_children()` (issue #4740). When either handler scans a resource location, it calls `discover_devcontainers()` after discovering `fs-directory` children. Devcontainer configurations are detected at the following locations (relative to the resource root): 1. `.devcontainer/devcontainer.json` 2. `.devcontainer.json` (root-level) 3. `.devcontainer//devcontainer.json` (named configurations) ### Discovery Process 1. **Trigger**: `discover_children()` is called on a `git-checkout` or `fs-directory` resource. 2. **Scan**: `discover_devcontainers()` 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 with `provisioning_state: discovered`. - Named configurations carry the subdirectory name as `config_name`. ### 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 `. 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 [--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 [--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 wired (F23 resolved) | `discover_devcontainers()` is now called from `GitCheckoutHandler.discover_children()` and `FsDirectoryHandler.discover_children()`. Devcontainer configs are auto-detected when `discover_children()` is invoked. | Completed in issue #4740. | | 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 ` 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)