Implemented lazy container activation for devcontainer-instance resources with ContainerLifecycleState enum tracking six states (inactive, starting, active, stopping, stopped, error) with validated transitions. Extended DevcontainerHandler with devcontainer up CLI integration and JSON output parsing for container start. Added periodic health checking via devcontainer exec ping with configurable interval. Added agents resource stop and agents resource rebuild CLI commands for manual lifecycle control. Wired session close and plan completion hooks to automatic container cleanup. Includes lifecycle state persistence in resource registry with timestamped transitions. Added Behave BDD tests, Robot integration tests, and ASV activation latency benchmarks. - Added remoteWorkspaceFolder absolute-path validation - Aligned spec: handler name, rebuild types, --yes flag on stop/rebuild - Added registry re-read in stop_container success path for consistency - Added session_id field to ContainerLifecycleTracker for scoped cleanup - Scoped stop_all_active_containers to session_id when provided - Wired _cleanup_devcontainers into fail_apply and fail_execute - Wired start_health_check into activate_container success path - Restructured facade session close to always run container cleanup even without session service (F4) - Re-read tracker from registry in activate_container success path - Added evict_terminal_trackers to cap registry growth - Updated devcontainer_resources.md: health check auto-start, scoped cleanup hooks, known limitations for eviction and sandbox_strategy - Wired evict_terminal_trackers into stop_all_active_containers so terminal-state trackers are actually evicted in production - Made stop_container idempotent: returns early when container is already in a terminal state instead of raising ValueError - Fixed benchmark health check thread leak in TimeActivationLatency by clearing registry after each timing loop - Added rebuild pass-through (--reset-container flag to devcontainer up) - Added host_workspace_path field on ContainerLifecycleTracker so health probes use the host-side path for devcontainer exec - Wired lazy activation into DevcontainerHandler.resolve() for devcontainer-instance resources in non-running states - Changed _default_strategy from SNAPSHOT to NONE (container itself provides isolation; SandboxFactory raises NotImplementedError for snapshot) - Restricted _STOPPABLE_TYPES to devcontainer-instance only (container-instance is not directly stoppable via CLI) ISSUES CLOSED: #514
12 KiB
Devcontainer Resources
Devcontainer resources integrate the Development Containers Specification 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 duringproject link-resourceor any other production code path. Auto-discovery will be wired in a follow-up PR. For now, devcontainer-instance resources must be added manually viaagents 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):
.devcontainer/devcontainer.json.devcontainer.json(root-level)
Discovery Process (Planned)
- Trigger: Linking a
git-checkoutorfs-directoryresource. - Scan: The discovery module checks for configuration files at the well-known paths listed above.
- Validate: Each discovered file is parsed as JSON. Invalid files are skipped with a warning.
- Register: For each valid configuration:
- A
devcontainer-instancechild resource is created under the parent resource. - A
devcontainer-filechild resource is created under the devcontainer instance, pointing to the JSON file.
- A
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:
- Transition to
building. - Run
devcontainer up --workspace-folder <location>. - Parse JSON output for
containerIdandremoteWorkspaceFolder. - On success: transition to
running. - 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.mdfor 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_intervalon the lifecycle tracker). - On failure: transitions to
failedviastopping. - 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:
AcpLocalFacade._handle_session_close— session closePlanLifecycleService.complete_apply— plan successPlanLifecycleService.fail_apply— plan apply failurePlanLifecycleService.fail_execute— plan execute failurePlanLifecycleService.cancel_plan— plan cancellation
CleanupService.stop_active_devcontainers(session_id="...")
CLI Usage
# 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
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
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). |
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.