Files
cleveragents-core/docs/reference/devcontainer_resources.md
HAL9000 3d7afb4378 fix(resource): wire discover_devcontainers() into git-checkout and fs-directory handlers
GitCheckoutHandler.discover_children() and FsDirectoryHandler.discover_children()
now call discover_devcontainers() after scanning for fs-directory children. Any
.devcontainer/devcontainer.json or root-level .devcontainer.json found at the
resource location is registered as a devcontainer-instance child resource with
provisioning_state: discovered. Named configurations are also discovered and carry
the configuration name in the config_name property.

This wires the previously-isolated discover_devcontainers() function into the
production code path, enabling the spec's zero-configuration devcontainer experience.

ISSUES CLOSED: #4740
2026-05-07 23:49:20 +00:00

13 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

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
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 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