# 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/<name>/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 <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 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 <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)
