From 05310391c12b52cdd0babd8f5dd75ae95d2d06e5 Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman Date: Sun, 1 Mar 2026 19:33:10 +0000 Subject: [PATCH] docs(spec): add devcontainer integration and resource type inheritance Created ADR-042 (Resource Type Inheritance) defining single-inheritance `inherits` field on resource type definitions with field resolution, collection merging, handler inheritance, polymorphic tool binding, auto-discovery, and DAG query matching. Max depth 5, single inheritance. Created ADR-043 (Devcontainer Integration) defining devcontainer-instance as a subtype of container-instance with lazy activation lifecycle, devcontainer.json parsing, three container-project association patterns (auto-detect, explicit mount, clone-into), and execution environment routing with a 6-level precedence chain. Specification updates across 20+ sections: - Glossary: Resource Type Inheritance, Devcontainer, Execution Environment - Resource type YAML schema: `inherits` field with structure reference - Handler, sandbox strategy, and coherence tables: devcontainer-instance - Auto-discovery: devcontainer detection subsection with WBS diagram - Tool capability metadata: structured environment subfields - Technology stack: devcontainer CLI row - Project model: execution environment subsection with YAML example - CLI agents resource add: --mount, --clone-into flags + 4 new examples - CLI agents plan use: --execution-environment, --execution-env-priority - CLI agents project context set: same execution environment flags - Command synopsis block: updated for all new flags - Execution environment routing section with precedence table + algorithm - Resource type YAML Example 6: Devcontainer Instance (inherited type) - End-to-end Example 16: Devcontainer-Driven Development - End-to-end Example 17: Explicit Container with Directory Mount - End-to-end Example 18: Container with Remote Repo Clone Also fixed ADR index: added missing ADR-036 through ADR-040 entries, updated next ADR number to 044. CONTRIBUTING.md fixes backported from sister project: - Fixed State label capitalization (State/In Progress -> State/In progress, State/In Review -> State/In review) to match label definitions. - Subtasks section: changed from optional to required, with exception clause for trivially simple issues. - Parent links: updated to use Forgejo dependency system instead of textual references in issue descriptions. - Fixed nox session flag typos: nox -e -> nox -s (7 occurrences). - Replaced "Epics and Legendaries" section with comprehensive "Ticket Type Hierarchy" defining the three-tier hierarchy (Issue -> Epic -> Legendary) with formal criteria tables, cross-cutting rules for hierarchy enforcement, completion semantics, promotion/demotion, and milestone relationship rules. - Fixed broken internal links referencing old "Epics and Legendaries" anchor to use "Ticket Type Hierarchy" (3 occurrences). - Traceability example: replaced project-specific code reference with generic example. - Removed stray horizontal rule before Project-Specific Guidelines. ISSUES CLOSED: #491 --- CONTRIBUTING.md | 186 ++- docs/adr/ADR-039-container-resource-types.md | 2 + docs/adr/ADR-042-resource-type-inheritance.md | 256 ++++ docs/adr/ADR-043-devcontainer-integration.md | 453 +++++++ docs/adr/index.md | 9 +- docs/specification.md | 1047 ++++++++++++++++- 6 files changed, 1901 insertions(+), 52 deletions(-) create mode 100644 docs/adr/ADR-042-resource-type-inheritance.md create mode 100644 docs/adr/ADR-043-devcontainer-integration.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f3e7fe672..4e28f933d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -574,7 +574,7 @@ or conditional testing behavior. particular purpose, update it rather than creating a new file elsewhere. - **Traceability:** When a decision impacts future work, reference the relevant code by its logical location — for example, module path, class name, and method name (e.g., - `cleveragents.application.services.PlanLifecycleService.use_action`). Include the commit hash + `mypackage.services.user_service.UserService.authenticate`). Include the commit hash at the time of writing so that if names are later refactored, someone can trace the original location through version control history. **Never reference code by line number** (`file_path:line_number`) as line numbers shift with every edit and become misleading quickly. @@ -621,8 +621,8 @@ Every issue must include: be assigned to them but is not required. Issues in `State/Unverified` may optionally have a milestone — it is not required at creation time, but is typically assigned by a maintainer during triaging when the issue is moved to `State/Verified`. Once a non-Epic, non-Legendary - issue is in any active state (`State/Verified`, `State/In Progress`, `State/Paused`, - `State/In Review`, or `State/Completed`), a milestone is mandatory. If no appropriate + issue is in any active state (`State/Verified`, `State/In progress`, `State/Paused`, + `State/In review`, or `State/Completed`), a milestone is mandatory. If no appropriate milestone exists, discuss with the project owner before proceeding. - **Branch/Tag (Ref):** Forgejo issues have a **branch/tag** field (the `Ref` field on the issue form) that associates the issue with a specific branch or tag in the repository. @@ -630,7 +630,7 @@ Every issue must include: - **Development issues** (features, fixes, chores, refactoring — any issue where a branch is created for the work): the Ref field must be set to the **same branch** named in the issue body's Metadata section. This field becomes required when the issue moves to - `State/In Progress` or later, and must always reference a **branch**, not a tag. Setting + `State/In progress` or later, and must always reference a **branch**, not a tag. Setting this field allows Forgejo to associate the issue with the correct branch in its UI and link tracking. - **Non-development issues** (bug reports, questions, support requests, and other issues @@ -667,11 +667,12 @@ Every issue must include: - **Commit Message**: `feat(cli): add tool and validation commands` - **Branch**: `feature/m3-tool-cli` ``` - - **Subtasks** *(optional but highly recommended)*: A section (typically labeled - `## Subtasks`) containing a checklist of discrete work items that break the issue down into - smaller, trackable steps. Each subtask should be a Markdown checkbox item (`- [ ]`). This - makes progress visible and helps contributors and reviewers understand the scope of work at - a glance. Example: + - **Subtasks** *(required)*: A section (typically labeled `## Subtasks`) containing a + checklist of discrete work items that break the issue down into smaller, trackable steps. + Each subtask must be a Markdown checkbox item (`- [ ]`). Subtasks are required for all + issues except those where the work is so straightforward that decomposition would be + trivial (e.g., a single-line configuration change). This makes progress visible and helps + contributors and reviewers understand the scope of work at a glance. Example: ```markdown ## Subtasks - [ ] Implement input validation for configuration parser @@ -700,11 +701,15 @@ Every issue must include: **merged** before this issue is marked done. ``` - **Parent link(s):** If the issue is part of a larger effort, link it to its parent Epic or - Legendary (see [Epics and Legendaries](#epics-and-legendaries)). An issue may have more than - one parent — for example, an end-to-end test issue might belong to both a testing Epic and - the thematic Epic whose feature it validates. All non-Epic, non-Legendary issues should be - linked to at least one parent when one exists. Reference parents in the issue description - (e.g., "Epic: #42" or "Part of #42 and #85"). + Legendary using Forgejo's dependency system (see + [Linking and Dependencies](#linking-and-dependencies) and + [Ticket Type Hierarchy](#ticket-type-hierarchy)). An issue may have more than one parent — + for example, an end-to-end test issue might belong to both a testing Epic and the thematic + Epic whose feature it validates. All non-Epic, non-Legendary issues should be linked to at + least one parent when one exists. **Do not reference parent tickets by number in the issue + description body.** Instead, open the child ticket and add the parent under "depends on," or + open the parent and add the child under "blocks." This creates a machine-readable directed + link that Forgejo tracks and displays automatically. - **Blocking link(s):** If the issue blocks or is blocked by other issues, these dependencies must be recorded at creation time when known. Link to any issues that this issue blocks, and any issues that block this one (e.g., "Blocked by #57", "Blocks #63"). See @@ -775,8 +780,8 @@ Type labels classify the nature of the work: | `Type/Bug` | A defect in existing functionality. Something that was working or was expected to work is broken or behaving incorrectly. | | `Type/Feature` | A new capability or user story. This issue describes functionality that does not yet exist. Also referred to as a "User Story." | | `Type/Task` | A unit of technical or administrative work that is not directly a bug fix or a new feature (e.g., refactoring, updating dependencies, improving documentation, infrastructure work). | -| `Type/Epic` | A large body of work that can be broken down into multiple smaller issues. See [Epics and Legendaries](#epics-and-legendaries). | -| `Type/Legendary` | An exceptionally large body of work, bigger than an Epic, representing a major initiative. See [Epics and Legendaries](#epics-and-legendaries). | +| `Type/Epic` | A large body of work that can be broken down into multiple smaller issues. See [Ticket Type Hierarchy](#ticket-type-hierarchy). | +| `Type/Legendary` | An exceptionally large body of work, bigger than an Epic, representing a major initiative. See [Ticket Type Hierarchy](#ticket-type-hierarchy). | #### Special Labels @@ -844,28 +849,123 @@ and back to `State/In progress` once the blocker is resolved. At any point, a maintainer may decide that an issue will not be addressed and move it to `State/Wont Do`. The reason must be documented in a comment on the issue. -### Epics and Legendaries +### Ticket Type Hierarchy -Epics and Legendaries are large-scale work items used to organize and group related issues. +This project uses a three-tier ticket hierarchy to organize work from strategic objectives +down to individual implementation tasks. Each tier has a formal definition, a target audience, +and strict rules governing its use. -- An **Epic** (`Type/Epic`) represents a large body of work that is broken down into multiple - smaller issues (bugs, features, tasks). Epics provide a high-level view of a feature or - initiative and track progress across their child issues. -- A **Legendary** (`Type/Legendary`) is even larger than an Epic. Legendaries represent major - initiatives or milestones that may encompass multiple Epics. +``` +Issue → Epic → Legendary +(commit) (capability) (strategic pillar) +``` -**Rules for Epics and Legendaries:** +- **Issues** are the atomic unit of work. Each issue corresponds to exactly one commit. + Developers work at this level. +- **Epics** group related issues into a demonstrable capability. Project managers and tech + leads work at this level. +- **Legendaries** group related epics into a strategic pillar of the project. The project + owner works at this level. -- Epics and Legendaries are organizational containers. They are not implemented directly — the - actual work is done in the child issues linked to them. -- All non-Epic, non-Legendary issues should be linked to a parent Epic or Legendary when one - exists. This ensures that all work is traceable to a larger objective. -- When creating a new issue, check whether an existing Epic or Legendary covers the area you - are working on. If it does, link your issue to it by referencing the Epic or Legendary in - your issue description (e.g., "Part of #42"). -- If you are proposing a large body of work that does not fit under an existing Epic, consider - creating a new Epic issue to serve as the parent, then creating individual issues for the - constituent tasks. +#### Issues (Atomic Work Unit) + +Issues are the leaf nodes of the hierarchy and the unit of work that developers implement. +Every issue maps to exactly one commit. The structural requirements for issue descriptions +(title, labels, metadata, subtasks, acceptance criteria, definition of done) are described +in [Creating Issues](#creating-issues). The criteria below define what qualifies as a +well-scoped issue. + +| # | Criterion | Definition | +|---|-----------|------------| +| 1 | **Atomicity** | The smallest meaningful unit of work that produces a single verifiable change to the codebase. If describing the change requires "and" between two unrelated actions, it should be two separate issues. | +| 2 | **Single Commit** | Corresponds to exactly one commit. One issue produces one commit. The commit must be atomic, self-contained, buildable, and testable in isolation per the [Commit Scope and Quality](#commit-scope-and-quality) guidelines. | +| 3 | **Single Responsibility** | Addresses exactly one concern — one bug fix, one function addition, one refactor, one test addition, one documentation update. Mixed concerns require separate issues. | +| 4 | **Assignability** | Can be fully owned and executed by a single developer without requiring synchronization with other in-flight work. | +| 5 | **Verifiability** | Has a clear, binary verification criterion stated in the issue description. The change either passes verification or it does not. | +| 6 | **Self-Containment** | All context needed to implement the change is present in the issue description, its parent Epic, or clearly referenced artifacts. A developer should not need to seek clarification to begin work. | +| 7 | **Implementation Independence** | Can ideally be implemented, reviewed, and merged without blocking on other issues in the same Epic. Where ordering dependencies exist, they must be explicitly documented using the [Linking and Dependencies](#linking-and-dependencies) process. | +| 8 | **Subtask Decomposition** | Must include a subtask checklist in the issue description (see [Creating Issues](#creating-issues)) that breaks the work into discrete, trackable steps. Subtasks are required for all issues except those where the work is so straightforward that decomposition would be trivial (e.g., a single-line configuration change). Subtasks are documentation within the issue body, not child tickets. | +| 9 | **Leaf Node** | Issues are leaf nodes in the ticket hierarchy — they have no child tickets. If an issue requires decomposition into multiple commits during implementation, it must be promoted to an Epic and broken into separate issues. | +| 10 | **Mandatory Parent** | Must belong to at least one Epic. Orphan issues are not permitted — every atomic change serves a larger capability. An issue may belong to more than one Epic when it genuinely serves multiple capabilities (e.g., an integration test that validates two Epics), but multi-parenting should be the exception rather than the rule. | +| 11 | **Finite Completion** | Done when the single commit is merged and all verification criteria pass. No ongoing or recurring obligation. Reopening is not permitted — if follow-up work is needed, a new issue is created. | + +#### Epics (Capability Unit) + +Epics represent demonstrable capabilities composed of multiple issues. They are the level at +which project managers and tech leads track progress and coordinate work across developers. +Every Epic must produce an outcome that can be shown to a stakeholder. + +| # | Criterion | Definition | +|---|-----------|------------| +| 1 | **Demonstrable Outcome** | Completion produces an outcome that can be demonstrated to a stakeholder — a working feature, a visible improvement, a passing integration test, a live system behavior. Not "code was written" but "here is something you can see, invoke, or verify." Every Epic must answer the question: "What will I show someone when this is done?" If there is no demonstration, the Epic is not a real capability unit. | +| 2 | **Thematic Coherence** | All child issues share a unified goal. Removing any child issue would leave the capability incomplete or degraded. Adding an unrelated issue would violate the theme. | +| 3 | **Own Acceptance Criteria** | Has explicit acceptance criteria beyond "all child issues are closed." The Epic defines what "this capability works end-to-end" means — an integration behavior, a user-visible outcome, or a systemic property that only emerges from the children working together. | +| 4 | **Full Decomposability** | Can be completely decomposed into issues at planning time. If it cannot be broken into concrete implementation steps, it is too vague and must be refined before work begins. | +| 5 | **Bounded Scope** | Has a clear, finite endpoint. The set of issues required is knowable in advance (though subject to refinement). Open-ended or exploratory work must be scoped into a concrete deliverable before it qualifies as an Epic. | +| 6 | **Minimum Composition** | Must contain at least two child issues. If only one issue is needed, the work does not warrant Epic-level coordination and should be filed as an issue under an existing Epic. | +| 7 | **Coordination Boundary** | Represents the level at which cross-developer dependencies and integration concerns are managed. The Epic owner ensures children are sequenced correctly and the integrated result works. | +| 8 | **Milestone Affinity** | Typically aligns with a single milestone. May straddle at most one milestone boundary (some child issues in milestone N, others in N+1), but should not scatter across many milestones — that signals it should be split or is actually a Legendary in disguise. | +| 9 | **Mandatory Parent** | Must belong to at least one Legendary. No Epic exists outside a strategic pillar. | +| 10 | **Finite Completion** | Done when all child issues are closed AND the Epic's own acceptance criteria are independently verified through demonstration. The Epic is then closed permanently — additional work in the same area is a new Epic, not a reopening. | + +#### Legendaries (Strategic Pillar) + +Legendaries are the highest level of the ticket hierarchy. They represent the major strategic +pillars of the project — the essential dimensions that must be realized for the project to be +considered successful. The project owner tracks progress at this level. + +| # | Criterion | Definition | +|---|-----------|------------| +| 1 | **Strategic Alignment** | Represents a major architectural dimension, business objective, or foundational pillar of the project. Each Legendary answers the question: "What is one of the essential things this project must achieve to be considered successful?" | +| 2 | **Articulated End State** | Has a clearly written terminal condition — a concrete, verifiable description of what "this pillar is fully realized" means. This prevents scope creep and perpetual Legendaries. If the end state cannot be articulated in a single paragraph, the Legendary is too vague. | +| 3 | **Multi-Milestone Span** | Naturally spans multiple milestones, reflecting the evolutionary and iterative nature of strategic goals. A Legendary that fits entirely within one milestone is likely an Epic in disguise. | +| 4 | **Epic Grouping** | Groups Epics that share a strategic theme, even when those Epics address different functional areas or technical layers. The unifying factor is strategic intent, not implementation similarity. | +| 5 | **Minimum Composition** | Must contain at least two child Epics. If only one Epic is needed, the work does not represent a strategic pillar and should be modeled as an Epic under an existing Legendary. | +| 6 | **Relative Independence** | Legendaries should be as independent as practical — minimal cross-Legendary blocking dependencies. Where dependencies exist, they must be explicitly documented. Two Legendaries that are tightly coupled should be considered for merger. | +| 7 | **Progress Measurability** | Progress is measured by Epic completion, not issue counts. The project owner should be able to glance at Legendary status and understand what fraction of the strategic pillar is realized. | +| 8 | **Owner Accountability** | Represents the dimensions along which the project owner evaluates project health and makes strategic prioritization decisions. | +| 9 | **No Parent** | Legendaries are the root of the ticket hierarchy. They have no parent ticket. | +| 10 | **Finite Completion** | Done when all child Epics are closed AND the articulated end state is independently verified. A Legendary is then closed permanently. If new strategic needs emerge in the same domain, a new Legendary is created — the old one is not reopened. | + +#### Cross-Cutting Rules + +**Hierarchy** + +- The ticket hierarchy is a strict tree with limited multi-parenting: Issue → Epic → + Legendary. No skip-level parenting is permitted (an issue cannot be a direct child of a + Legendary). +- Each Epic has exactly one parent Legendary. Each issue has at least one parent Epic + (multi-parenting is permitted for issues but should be the exception). Legendaries have no + parent. +- No circular dependencies at any level of the hierarchy. +- **Promotion and demotion:** If an issue requires decomposition into multiple commits during + implementation, it must be promoted to an Epic and broken into separate issues. If an Epic + collapses to a single issue, it should be demoted (the issue absorbed into another Epic). If + a Legendary is left with only one child Epic, evaluate whether the Legendary should be + absorbed into another Legendary. + +**Completion** + +- Every ticket at every level must have an articulated, verifiable end state defined at + creation time. +- A parent ticket is complete only when: (a) all of its children are complete, AND (b) its own + acceptance criteria are independently met. Children being done is necessary but not + sufficient. +- No ticket at any level may remain open indefinitely. If the scope of work changes, close the + existing ticket with a comment explaining the change and open a new ticket with the revised + scope. Do not reopen closed tickets. + +**Relationship to Milestones and Versions** + +- Milestones and versions are a temporal planning tool. They are orthogonal to the ticket + hierarchy — they represent *when* work happens, not *how* it is organized. +- Issues (atomic work units) are assigned to milestones. This is where scheduling lives. +- Epics and Legendaries are **not** assigned to milestones. They inherit temporal presence from + their child issues — an Epic "participates in" whichever milestones its issues are assigned + to, and a Legendary spans whichever milestones its Epics touch. +- There must be no one-to-one coupling between any hierarchy level and milestones. Multiple + Legendaries will contribute work to the same milestone. A single Legendary will have work + across multiple milestones. This is expected and correct. ### Linking and Dependencies @@ -962,8 +1062,6 @@ Contributors can help the triaging process by writing clear, complete issues tha the required fields described in [Creating Issues](#creating-issues). A well-written issue is significantly more likely to be verified quickly and acted on. ---- - ## Project-Specific Guidelines The following guidelines are specific to this project and supplement the general rules above @@ -1019,20 +1117,20 @@ Run tests using `nox`. Do not invoke `behave`, `robot`, or similar runners direc session is missing required tooling, add the dependency to the session before rerunning. - **All tests (including static checks):** `nox` -- **Unit tests only:** `nox -e unit_tests` -- **Integration tests:** `nox -e integration_tests` -- **Benchmarks:** `nox -e benchmark` -- **Coverage report:** `nox -e coverage_report` +- **Unit tests only:** `nox -s unit_tests` +- **Integration tests:** `nox -s integration_tests` +- **Benchmarks:** `nox -s benchmark` +- **Coverage report:** `nox -s coverage_report` **Coverage threshold:** Unit test coverage must remain above **97%** at all times. Coverage is -measured with `nox -e coverage_report` and is enforced in CI. This threshold is stricter than +measured with `nox -s coverage_report` and is enforced in CI. This threshold is stricter than the general minimum of 85% described in [Review and Merge Requirements](#review-and-merge-requirements) — for this project, **97% is the enforced merge gate**. Pull Requests that cause coverage to drop below 97% will not be merged. **Performance benchmarks:** Include ASV (airspeed velocity) benchmarks for performance-sensitive -code. Benchmarks are run with `nox -e benchmark`. +code. Benchmarks are run with `nox -s benchmark`. ### Merge Requirements @@ -1042,14 +1140,14 @@ project with the following project-specific details: - **Automated checks** are run via `nox`. The full suite (`nox` with no arguments) must pass, which includes unit tests, integration tests, type checking (Pyright), linting, formatting, and security scanning. -- **Coverage gate:** 97% (not the general 85% minimum). Measured by `nox -e coverage_report`. +- **Coverage gate:** 97% (not the general 85% minimum). Measured by `nox -s coverage_report`. - **Pre-commit hooks** (configured in `.pre-commit-config.yaml`) enforce commit quality locally. All hooks must pass before a commit is accepted. See [Commit Quality Enforcement](#commit-quality-enforcement) for setup instructions. ### Static Type Checker -All code must pass **Pyright** via `nox -e typecheck`, which is also run as part of the default +All code must pass **Pyright** via `nox -s typecheck`, which is also run as part of the default test suite with `nox`. Under no circumstances should type checking be ignored — never modify configuration files to disable it, and never use inline comments (such as `# type: ignore`) to suppress type checking errors. diff --git a/docs/adr/ADR-039-container-resource-types.md b/docs/adr/ADR-039-container-resource-types.md index 629f6b6b2..d2a9a96f1 100644 --- a/docs/adr/ADR-039-container-resource-types.md +++ b/docs/adr/ADR-039-container-resource-types.md @@ -274,6 +274,8 @@ The engine-specific details are encapsulated in the resource handler — the res | [ADR-036](ADR-036-resource-dag-operational-semantics.md) | Resource DAG Operational Semantics | Container types create new sandbox domains subject to the sandbox boundary algebra | | [ADR-038](ADR-038-cross-mechanism-sandbox-coordination.md) | Cross-Mechanism Sandbox Coordination | Container/host equivalence is the primary use case for cross-mechanism coordination | | [ADR-040](ADR-040-lsp-resource-types.md) | LSP Resource Types | LSP servers may run inside containers, referencing container executables | +| [ADR-042](ADR-042-resource-type-inheritance.md) | Resource Type Inheritance | Provides the inheritance mechanism enabling subtypes of container-instance (e.g., devcontainer-instance) | +| [ADR-043](ADR-043-devcontainer-integration.md) | Devcontainer Integration and Container-Project Association | Defines devcontainer-instance as a subtype of container-instance with auto-detection, container-project association patterns, and execution environment routing | ## Acceptance diff --git a/docs/adr/ADR-042-resource-type-inheritance.md b/docs/adr/ADR-042-resource-type-inheritance.md new file mode 100644 index 000000000..a09942171 --- /dev/null +++ b/docs/adr/ADR-042-resource-type-inheritance.md @@ -0,0 +1,256 @@ +# ADR-042: Resource Type Inheritance + +**Status:** Accepted +**Date:** 2026-03-01 +**Supersedes:** None +**Author(s):** Jeffrey Phillips Freeman +**Approver(s):** Jeffrey Phillips Freeman + +## Context + +CleverAgents defines resource types as flat, independent definitions — each type declares its own properties, capabilities, child types, sandbox strategy, and handler from scratch. This works well for types that are fundamentally different (e.g., `git-checkout` vs. `fs-directory` vs. `container-instance`), but creates significant problems when types share a core identity with specialized behavior. + +The container resource type hierarchy (ADR-039) introduced seven container types that are engine-agnostic (Docker, Podman, containerd, etc.). However, modern development workflows require further specialization: devcontainer-based environments (defined by the [Development Containers Specification](https://containers.dev)), docker-compose service instances, Kubernetes pod instances, and other container provisioning models all share the fundamental characteristics of a `container-instance` — they are running containers with mounts, ports, execution environments, and snapshot-based sandboxing — but each adds provisioning-specific configuration, auto-discovery rules, and lifecycle behavior. + +Without a type inheritance mechanism, each specialization would need to duplicate every field from `container-instance` in its own type definition, tools bound to `container-instance` would not automatically work with specialized types, and the auto-discovery system would need explicit enumeration of every possible container subtype rather than polymorphic matching. + +The same pattern appears in other parts of the type hierarchy: database types sharing common query interfaces, cloud resource types sharing API access patterns, and document types sharing structural properties. A general-purpose inheritance mechanism addresses all of these cases. + +## Decision + +CleverAgents introduces **single-inheritance resource type specialization** via an `inherits` field on resource type definitions. A subtype inherits all properties, capabilities, child type constraints, sandbox strategy, and handler behavior from its parent type, and can selectively override or extend any inherited field. Tool bindings, auto-discovery child type matching, and DAG queries that reference a parent type automatically include all subtypes — this is the core polymorphism guarantee. + +## Design + +### The `inherits` Field + +Resource type YAML definitions gain an optional `inherits` field specifying the parent type name: + +```yaml +name: devcontainer-instance +inherits: container-instance +description: "A container provisioned from a devcontainer.json configuration" + +# Only fields that differ from or extend container-instance need to be declared. +# All other fields (capabilities, sandbox_strategy, child_types, etc.) are inherited. + +cli_args: + # Inherited args from container-instance are available. + # Additional args specific to devcontainer: + - name: config-path + type: path + required: false + description: "Path to .devcontainer/devcontainer.json or .devcontainer/ directory" + - name: workspace-folder + type: path + required: false + description: "Host folder to mount as the container workspace" + +handler: + class: DevcontainerInstanceHandler + module: cleveragents.resources.handlers.devcontainer +``` + +### Inheritance Semantics + +#### Field Resolution + +When the system resolves a field for a resource type, it walks the inheritance chain from the most specific type to the root: + +1. If the subtype **declares** the field, use the subtype's value (override). +2. If the subtype **omits** the field, inherit from the parent. +3. Walk continues up the chain until a value is found or the root type is reached. + +This applies to all top-level fields: `description`, `physical`, `sandbox_strategy`, `capabilities`, `handler`, and scalar configuration values. + +#### Collection Field Merging + +Fields that are collections (`cli_args`, `child_types`, `parent_types`, properties) use **additive merging** by default: + +- **`cli_args`**: The subtype's CLI arguments are appended to the parent's arguments. If a subtype declares an argument with the same `name` as a parent argument, the subtype's definition overrides the parent's (same-name replacement). +- **`child_types`**: The subtype's allowed child types are merged with the parent's. The subtype can add new child types or override `auto_discover`/`max_count` for inherited child types by redeclaring them. +- **`parent_types`**: The subtype inherits all allowed parent types from the parent and can add additional ones. The parent type itself is implicitly an allowed parent. +- **Properties**: The subtype inherits all parent properties and can add or override individual properties. + +A subtype can explicitly **replace** (rather than merge) a collection field by declaring `_replace: true`: + +```yaml +# Replace parent's child_types entirely rather than merging +child_types_replace: true +child_types: + - type: custom-mount + auto_discover: true +``` + +#### Handler Inheritance + +The subtype's handler class should extend the parent's handler class in the Python implementation. The resource type system does not enforce this at the YAML level (it only knows class names), but the handler contract assumes Liskov substitutability — a subtype handler must honor all parent handler behaviors while extending them. + +```python +class DevcontainerInstanceHandler(ContainerInstanceHandler): + """Extends container-instance with devcontainer-specific provisioning.""" + + def discover_children(self, resource: ResourceRecord) -> list[ResourceRecord]: + # Call parent discovery (mounts, exec-env, ports) + children = super().discover_children(resource) + # Add devcontainer-specific discovery (features, workspace mount) + children.extend(self._discover_devcontainer_features(resource)) + return children + + def activate(self, resource: ResourceRecord) -> None: + """Build the devcontainer from devcontainer.json on first access.""" + self._build_devcontainer(resource) + super().activate(resource) +``` + +### Polymorphism Guarantees + +#### Tool Binding Polymorphism + +A tool with a resource slot typed to `container-instance` accepts any resource whose type is `container-instance` **or any type that inherits from it** (directly or transitively). The tool does not need to know about subtypes — it operates on the parent type's interface. + +```yaml +# This tool works with container-instance AND devcontainer-instance +# AND compose-instance AND any future subtype. +tool: + name: local/container-shell + resource_bindings: + - slot: target + type: container-instance # Matches all subtypes + access: read_write +``` + +Resolution: When binding a resource to a tool slot, the system checks `resource.type == slot.type OR resource.type.inherits_from(slot.type)`. + +#### Auto-Discovery Child Type Polymorphism + +When a parent resource type declares a child type, subtypes of that child type also satisfy the constraint: + +```yaml +# container-runtime allows container-instance children +child_types: + - type: container-instance + auto_discover: true + +# devcontainer-instance (inherits container-instance) is also +# a valid child of container-runtime — no configuration change needed. +``` + +#### DAG Query Polymorphism + +Queries against the resource DAG that filter by type automatically include subtypes: + +- `find_resources(type="container-instance")` returns container-instance resources AND devcontainer-instance resources AND any other subtypes. +- `find_resources(type="container-instance", exact=True)` returns only exact `container-instance` resources (opt-in exact matching). + +### Inheritance Chain Rules + +1. **Single inheritance only.** A type may declare at most one `inherits` value. Diamond inheritance (multiple parents) is not supported. +2. **Depth limit.** The inheritance chain is limited to 5 levels to prevent pathological hierarchies and ensure resolution performance. Built-in types occupy at most 2 levels (e.g., `container-instance` → `devcontainer-instance`). +3. **No circular inheritance.** The system validates at registration time that the `inherits` chain is acyclic. +4. **Built-in types as parents.** Custom types (namespaced) may inherit from built-in types (unnamespaced). Built-in types may inherit from other built-in types. Built-in types may **not** inherit from custom types. +5. **Stable parent contract.** Removing or renaming a parent type is a breaking change that invalidates all subtypes. The system prevents removal of a type that has registered subtypes. + +### Registration and Validation + +When a resource type with `inherits` is registered: + +1. The parent type must already be registered. +2. The system resolves the full inheritance chain and validates no cycles exist. +3. Merged fields are computed and cached for runtime performance. +4. Any `cli_args` name collisions between parent and subtype are resolved (subtype wins). +5. The handler class is validated as loadable (but Liskov substitutability is not checked at registration time — it is a development-time contract). + +### Impact on Existing Resource Types + +No existing resource types gain `inherits` fields in this ADR. The mechanism is introduced to enable ADR-043 (Devcontainer Integration) and future specialization patterns. The existing container types (`container-instance`, `container-mount`, etc.) remain root types with no parent. + +Potential future applications of type inheritance include: + +| Subtype | Parent | Purpose | +|---------|--------|---------| +| `devcontainer-instance` | `container-instance` | Devcontainer.json-provisioned containers (ADR-043) | +| `compose-instance` | `container-instance` | Docker Compose service instances | +| `kubernetes-pod` | `container-instance` | Kubernetes pod instances | +| `postgresql-database` | (custom `database`) | PostgreSQL-specific database type | + +## Constraints + +- A resource type definition must have at most one `inherits` value (single inheritance). +- The inheritance chain must not exceed 5 levels. +- The inheritance chain must be acyclic — validated at registration time. +- Built-in types must not inherit from custom (namespaced) types. +- Removing a parent type is prohibited while subtypes exist in the registry. +- Subtype handlers must honor the parent handler's behavioral contract (Liskov substitutability). This is a development-time responsibility enforced by integration tests, not by the type system. + +## Consequences + +### Positive + +- Specialized resource types (devcontainer, compose, kubernetes) share a common base without field duplication. +- Tools bound to parent types automatically work with all subtypes — no tool changes needed when new subtypes are introduced. +- Auto-discovery and DAG queries are polymorphic by default, reducing configuration burden. +- The pattern is general-purpose and applicable beyond containers (databases, cloud resources, documents). + +### Negative + +- Adds complexity to the resource type resolution system (field merging, chain walking, polymorphic matching). +- Collection field merging rules (additive vs. replace) add cognitive overhead for type authors. +- Handler inheritance assumes Python class hierarchy alignment, creating a coupling between YAML definitions and Python implementation structure. + +### Risks + +- Deep inheritance chains (approaching the 5-level limit) may make field resolution confusing. Mitigated by the depth limit and the expectation that most hierarchies are 2 levels. +- Polymorphic tool binding could cause unexpected matches if a subtype inherits a parent type that the tool author did not intend to cover. Mitigated by `exact=True` matching where precision is needed. +- Handler Liskov substitutability violations may cause runtime errors. Mitigated by integration test requirements. + +## Alternatives Considered + +**Discriminated variant pattern** — Add a `variant` or `source` field to `container-instance` instead of creating subtypes. Different handler classes would be dispatched based on the variant value. This is simpler but less extensible: every new variant modifies the base type definition, tools cannot distinguish between variants, and custom types cannot participate in the hierarchy. Rejected because it fails the open/closed principle — the base type must be modified for every extension. + +**Composition over inheritance** — Attach specialized behavior via linked "capability" resources rather than type inheritance. For example, a `devcontainer-config` resource would be linked to a plain `container-instance`. This avoids type hierarchy complexity but breaks the polymorphism guarantee (tools bound to `container-instance` would not know about the devcontainer capabilities) and makes auto-discovery more complex (the system would need to discover both the container and its configuration resource separately). + +## Compliance + +- **Schema validation tests**: Resource type YAML with `inherits` fields parse correctly, with valid and invalid chains. +- **Field resolution tests**: Subtype field resolution follows the documented precedence (subtype > parent) for scalar and collection fields. +- **Collection merging tests**: `cli_args`, `child_types`, and `parent_types` merge correctly with additive and replace modes. +- **Polymorphism tests**: Tool binding, auto-discovery child matching, and DAG queries correctly include subtypes. +- **Cycle detection tests**: Circular `inherits` chains are rejected at registration time. +- **Depth limit tests**: Chains exceeding 5 levels are rejected. +- **Parent removal tests**: Attempting to remove a type with registered subtypes fails with a descriptive error. + +## Related ADRs + +| ADR | Title | Relationship | +|-----|-------|-------------| +| [ADR-008](ADR-008-resource-system.md) | Resource System | Extends the resource type registry with inheritance semantics | +| [ADR-011](ADR-011-tool-system.md) | Tool System | Tool resource bindings gain polymorphic subtype matching | +| [ADR-036](ADR-036-resource-dag-operational-semantics.md) | Resource DAG Operational Semantics | DAG queries gain polymorphic type filtering | +| [ADR-037](ADR-037-tool-reachability-and-access-projection.md) | Tool Reachability and Access Projection | Reachability computation considers subtype relationships | +| [ADR-039](ADR-039-container-resource-types.md) | Container and Execution Environment Resource Types | Container types are the primary use case for type inheritance | +| [ADR-043](ADR-043-devcontainer-integration.md) | Devcontainer Integration and Container-Project Association | First concrete subtype (`devcontainer-instance`) built on this mechanism | + +## Acceptance + +### Votes For + +| Voter | Comment | +|-------|---------| +| Jeffrey Phillips Freeman | Single-inheritance with polymorphic tool binding enables extensible container specialization without duplicating type definitions | + +**Total: 1** + +### Votes Against + +| Voter | Comment | +|-------|---------| + +**Total: 0** + +### Abstentions + +| Voter | Comment | +|-------|---------| + +**Total: 0** diff --git a/docs/adr/ADR-043-devcontainer-integration.md b/docs/adr/ADR-043-devcontainer-integration.md new file mode 100644 index 000000000..ed910f810 --- /dev/null +++ b/docs/adr/ADR-043-devcontainer-integration.md @@ -0,0 +1,453 @@ +# ADR-043: Devcontainer Integration and Container-Project Association + +**Status:** Accepted +**Date:** 2026-03-01 +**Supersedes:** None +**Author(s):** Jeffrey Phillips Freeman +**Approver(s):** Jeffrey Phillips Freeman + +## Context + +Modern software development increasingly relies on containerized development environments for reproducibility, isolation, and consistency. The [Development Containers Specification](https://containers.dev) (devcontainers) has emerged as a widely adopted standard for defining these environments via a `.devcontainer/devcontainer.json` configuration file. IDEs (VS Code, IntelliJ), cloud development environments (GitHub Codespaces, Gitpod), and CI systems recognize this standard. + +CleverAgents already models containers as first-class resources (ADR-039) and supports resource type inheritance (ADR-042). However, the system has no mechanism to: + +1. **Auto-detect** devcontainer configurations when a project's git repository or directory contains a `.devcontainer/` folder. +2. **Associate containers with projects** through resource linking — whether the container is a devcontainer, an explicit container with a mounted directory, or a container that clones a remote repository. +3. **Route tool execution** into the appropriate container when a project has one or more container execution environments, with clear precedence rules when multiple containers and devcontainers coexist. + +Without these capabilities, users must manually configure container environments outside of CleverAgents and ensure tools execute in the right context — losing the system's ability to reason about execution environments, sandbox coordination, and tool dependency resolution. + +## Decision + +CleverAgents introduces: + +1. **`devcontainer-instance`** — a built-in resource type that inherits from `container-instance` (using the resource type inheritance mechanism from ADR-042), representing a container defined by a `devcontainer.json` configuration. Auto-discovered when a `git-checkout` or `fs-directory` resource contains a `.devcontainer/` directory. + +2. **Container-project association patterns** — three standard patterns for linking containers to projects via the existing resource linking mechanism, with mount and clone semantics expressed through `agents resource add` arguments. + +3. **Execution environment routing** — a precedence-based system for determining where tools execute (host vs. container), configurable at project, plan, and resource scope with explicit override semantics. + +## Design + +### `devcontainer-instance` Resource Type + +`devcontainer-instance` inherits from `container-instance` and adds devcontainer-specific provisioning, auto-discovery, and lifecycle management. + +| Field | Value | +|-------|-------| +| **Inherits** | `container-instance` | +| **Kind** | physical | +| **Sandbox strategy** | `snapshot` (inherited) | +| **User-addable** | yes | +| **CLI arguments** | (inherited from `container-instance`) + `--config-path` (path to `.devcontainer/devcontainer.json` or `.devcontainer/` directory, optional), `--workspace-folder` (host folder to mount as workspace, optional) | +| **Allowed parents** | `container-runtime` (inherited), `git-checkout` (0..1), `fs-directory` (0..1) | +| **Allowed children** | (all inherited from `container-instance`: `container-mount`, `container-exec-env`, `container-port`) | +| **Auto-discovery** | Discovered from `git-checkout` or `fs-directory` when `.devcontainer/devcontainer.json` exists in the filesystem tree. Also auto-discovers inherited children (mounts, exec-env, ports) on activation. | +| **Capabilities** | read: true, write: true, sandbox: true, checkpoint: true (all inherited) | + +#### Properties + +All properties inherited from `container-instance` (`container_id`, `name`, `state`, `image_ref`, `engine_type`, etc.), plus: + +| Property | Type | Description | +|----------|------|-------------| +| `devcontainer_config` | object | Parsed contents of `devcontainer.json` | +| `config_path` | path | Absolute path to the `devcontainer.json` file | +| `workspace_folder` | path | Host folder mounted as the container workspace (from `devcontainer.json` `workspaceFolder` or CLI `--workspace-folder`) | +| `workspace_mount` | string | The workspace mount specification (from `devcontainer.json` `workspaceMount`) | +| `features` | list | Installed [devcontainer features](https://containers.dev/features) with version pins | +| `post_create_command` | string/list | Command(s) to run after container creation | +| `post_start_command` | string/list | Command(s) to run after container start | +| `post_attach_command` | string/list | Command(s) to run after attaching to the container | +| `remote_user` | string | The user to run commands as inside the container | +| `container_env` | map | Environment variables set inside the container | +| `provisioning_state` | enum | `discovered` \| `building` \| `active` \| `stopped` \| `failed` | + +#### Lifecycle: Lazy Activation + +Devcontainer resources follow a **lazy activation** model consistent with the system's lazy sandboxing philosophy: + +1. **Discovery** (`provisioning_state: discovered`): When `git-checkout` or `fs-directory` auto-discovery finds `.devcontainer/devcontainer.json`, a `devcontainer-instance` resource is created. The `devcontainer.json` is parsed to populate `devcontainer_config`, `features`, `workspace_folder`, and other configuration properties. **No container is built or started.** No child resources (mounts, exec-env, ports) are created yet — they are pending lazy activation. + +2. **Building** (`provisioning_state: building`): When a plan first needs to execute a tool inside the devcontainer, the system triggers the build. This uses the `devcontainer` CLI (if available) or falls back to direct Docker/Podman commands based on the devcontainer.json configuration. The image is built (from Dockerfile, image reference, or docker-compose), features are installed, and the container is created. + +3. **Activation** (`provisioning_state: active`): Once the container is running, the inherited `container-instance` auto-discovery runs — discovering `container-mount` children (workspace mount + any additional mounts from devcontainer.json), `container-exec-env` (executables available inside the container), and `container-port` (forwarded ports). Post-create and post-start commands are executed. + +4. **Deactivation** (`provisioning_state: stopped`): When the plan completes or the container is no longer needed, the container is stopped (not removed). Subsequent plans can reactivate it without rebuilding. + +5. **Failure** (`provisioning_state: failed`): If the build or activation fails, the resource enters the `failed` state with diagnostic information. Plans that require this execution environment will fail with a descriptive error. + +#### Devcontainer.json Parsing + +The handler parses `devcontainer.json` to extract: + +| devcontainer.json field | Mapped to | +|-------------------------|-----------| +| `image` | `image_ref` property (inherited from container-instance) | +| `build.dockerfile` / `dockerFile` | Build configuration for image creation | +| `dockerComposeFile` | Compose-based provisioning (future: `compose-instance` subtype) | +| `workspaceFolder` | `workspace_folder` property + `container-mount` child | +| `workspaceMount` | `workspace_mount` property | +| `features` | `features` property | +| `forwardPorts` | `container-port` children | +| `mounts` | Additional `container-mount` children | +| `postCreateCommand` | `post_create_command` property | +| `postStartCommand` | `post_start_command` property | +| `postAttachCommand` | `post_attach_command` property | +| `remoteUser` | `remote_user` property | +| `containerEnv` | `container_env` property | +| `remoteEnv` | Merged into `container_env` | + +#### Auto-Discovery from `git-checkout` and `fs-directory` + +The existing `GitCheckoutHandler` and `FilesystemHandler` auto-discovery is extended: + +1. During filesystem scanning, if a `.devcontainer/` directory is found: + a. Look for `devcontainer.json` inside it (required). + b. If found, create a `devcontainer-instance` child resource with `provisioning_state: discovered`. + c. The devcontainer's `workspace_folder` defaults to the `git-checkout` path (or `fs-directory` path). + d. Record the `container-mount` relationship between the devcontainer and the parent resource's `fs-directory` child — but do not create the `container-mount` resource yet (pending activation). + +2. Multiple `.devcontainer/` directories in nested subdirectories each produce separate `devcontainer-instance` resources. The devcontainer spec allows [multiple configurations](https://containers.dev/implementors/spec/#devcontainerjson) via named configurations in `.devcontainer//devcontainer.json`. + +3. The devcontainer resource is marked `auto: true` in the DAG (same as other auto-discovered children). + +**Updated `git-checkout` auto-discovery result (when `.devcontainer/` is present):** + +``` +git-checkout (local/web-app) + ├── git (repo metadata — branches, commits, trees, ...) + ├── fs-directory (worktree root) + │ ├── .devcontainer/ + │ │ └── devcontainer.json + │ ├── src/ + │ ├── tests/ + │ └── ... + └── devcontainer-instance (provisioning_state: discovered) + ├── [container-mount — pending activation] + ├── [container-exec-env — pending activation] + └── [container-port — pending activation] +``` + +### Container-Project Association Patterns + +Three standard patterns for associating containers with projects, all using the existing resource linking mechanism: + +#### Pattern 1: Devcontainer Auto-Detection + +The simplest and most common pattern. No explicit container configuration needed. + +```bash +# 1. Register a git-checkout that contains .devcontainer/ +agents resource add git-checkout local/web-app \ + --path /home/user/projects/web-app --branch main +# Output includes: devcontainer-instance discovered (from .devcontainer/devcontainer.json) + +# 2. Create project and link the resource +agents project create --description "Web application" \ + --resource local/web-app local/web-project + +# The devcontainer is automatically available as an execution environment +# for tools operating on local/web-app within local/web-project. +``` + +#### Pattern 2: Explicit Container with Directory Mount + +For projects that use a container but do not have a `.devcontainer/` configuration. + +```bash +# 1. Register the directory/repo +agents resource add git-checkout local/api-repo \ + --path /home/user/projects/api --branch main + +# 2. Register a container-instance with a mount to the existing resource +agents resource add container-instance local/api-container \ + --image node:20-slim \ + --mount local/api-repo:/workspace \ + --port 3000:3000 + +# 3. Link both to the project +agents project link-resource local/api-project local/api-repo +agents project link-resource local/api-project local/api-container +``` + +The `--mount` argument on `agents resource add container-instance` accepts two formats: + +- **Resource reference**: `--mount local/api-repo:/workspace` — references a registered resource by name. Creates a DAG `container-mount` child that links to the referenced resource's `fs-directory`. When the resource is a `git-checkout`, the mount targets the worktree root's `fs-directory` child. + +- **Raw host path**: `--mount /home/user/projects/api:/workspace` — specifies a host path directly. If the path matches an already-registered resource's location, links to that resource. Otherwise, auto-creates an anonymous `fs-directory` resource for the host path and links to it. + +Both formats specify the container-internal mount target after the colon (`:`) separator. + +The `--mount` flag is repeatable for multiple mount points. + +#### Pattern 3: Container with Remote Repo Clone + +For cloud/CI scenarios where the repository is remote and should be cloned into the container. + +```bash +# 1. Register a remote git repo (no local checkout) +agents resource add git local/upstream \ + --url git@github.com:org/api-service.git + +# 2. Register a container that will clone the repo on activation +agents resource add container-instance local/cloud-dev \ + --image python:3.13-slim \ + --clone-into local/upstream:/workspace \ + --port 8000:8000 + +# 3. Link both to the project +agents project link-resource local/cloud-project local/upstream +agents project link-resource local/cloud-project local/cloud-dev +``` + +The `--clone-into` argument records that when the container is activated, the referenced `git` resource should be cloned into the container at the specified path. On activation: + +1. The container is created from the specified image. +2. The `git` resource's repository is cloned into the container at the target path. +3. A `container-mount` child is created representing the cloned checkout. +4. The cloned checkout auto-discovers as a `git-checkout` child of the mount, creating virtual equivalence with the remote `git` resource's branches and commits. + +This pattern is particularly useful for: +- CI/CD pipelines where the code is not checked out on the host +- Cloud-based development environments +- Ephemeral execution environments for untrusted code + +### Execution Environment Routing + +When a project has one or more container execution environments (devcontainers, explicit containers, or both), the system must determine where each tool executes. This is governed by the **execution environment routing** model. + +#### Configuration Levels + +Execution environment preferences can be set at three levels: + +##### Project-Level + +Set via `agents project context set` or at project creation time: + +```bash +agents project context set \ + --execution-environment local/dev-container \ + --execution-env-priority fallback \ + local/my-project +``` + +Stored in the project configuration: + +```yaml +execution_environment: + default: local/dev-container + priority: fallback # fallback | override +``` + +##### Plan-Level + +Set via `agents plan use` arguments, overriding the project-level setting for the duration of the plan: + +```bash +agents plan use \ + --execution-environment local/staging-container \ + --execution-env-priority override \ + my-action local/my-project +``` + +##### Resource-Scoped (Auto-Detected) + +When a resource (e.g., `git-checkout`) has an auto-detected `devcontainer-instance` child, that devcontainer automatically becomes the execution environment for tools operating on that resource. This is not explicitly configured — it is inferred from the resource DAG. + +#### Priority Semantics + +The `priority` field controls how an explicitly configured execution environment interacts with auto-detected devcontainers: + +- **`fallback`** (default): The configured execution environment is used only when no devcontainer is auto-detected for the current resource or its ancestors. If a resource has a `.devcontainer/`, the devcontainer wins. + +- **`override`**: The configured execution environment is always used, regardless of whether a devcontainer is present. This is for cases where the user explicitly wants to force a specific container, overriding any devcontainer configurations in the codebase. + +#### Precedence Chain + +The full precedence chain for execution environment resolution, from highest to lowest priority: + +| Priority | Source | When Used | +|----------|--------|-----------| +| 1 | Plan-level with `priority: override` | Always wins when set | +| 2 | Project-level with `priority: override` | Wins if no plan-level override | +| 3 | Resource-scoped auto-detected devcontainer (nearest ancestor) | Wins over fallback-priority settings | +| 4 | Plan-level with `priority: fallback` | Used when no devcontainer detected for the current resource | +| 5 | Project-level with `priority: fallback` | Used when no devcontainer detected and no plan-level fallback | +| 6 | Host (no container) | Default when no execution environment is configured anywhere | + +#### Nearest-Ancestor Devcontainer Resolution + +When a resource hierarchy contains multiple `.devcontainer/` configurations at different levels, the system uses the **nearest ancestor** rule: + +1. Starting from the file or directory being operated on by a tool, walk **up** the filesystem hierarchy toward the resource root. +2. At each level, check if a `.devcontainer/devcontainer.json` exists. +3. Use the **first** (nearest) devcontainer found. + +Example: A monorepo with per-service devcontainers: + +``` +monorepo/ ← git-checkout root + ├── .devcontainer/ ← root devcontainer (Node.js) + │ └── devcontainer.json + ├── services/ + │ ├── api/ + │ │ ├── .devcontainer/ ← service-specific devcontainer (Python) + │ │ │ └── devcontainer.json + │ │ └── src/ + │ └── web/ + │ └── src/ + └── packages/ +``` + +- A tool operating on `services/api/src/main.py` uses the **Python devcontainer** (nearest: `services/api/.devcontainer/`). +- A tool operating on `services/web/src/index.ts` uses the **Node.js devcontainer** (nearest: root `.devcontainer/`). +- A tool operating on `packages/shared/lib.ts` uses the **Node.js devcontainer** (nearest: root `.devcontainer/`). + +#### Tool Execution Environment Compatibility + +Tools declare their execution environment compatibility in their capability metadata: + +```yaml +capability: + environment: + required: container | host | any # Where the tool CAN run + preferred: container | host # Where the tool PREFERS to run (optional) + specific: local/dev-container # A specific container required (optional) +``` + +- `required: any` (default) — tool can run on host or in any container. +- `required: container` — tool must run inside a container (e.g., tools that need specific container-only dependencies). +- `required: host` — tool must run on the host (e.g., tools that need direct hardware access). +- `preferred: container` — tool prefers container execution when available but falls back to host. +- `specific: local/dev-container` — tool requires a specific named container. + +When the execution environment routing selects a container but a tool requires `host`, the tool runs on the host. When no container is available but a tool requires `container`, the plan reports an error for that tool invocation. + +### Container-Instance CLI Argument Extensions + +The existing `container-instance` resource type gains new CLI arguments for mount and clone operations: + +| Argument | Type | Required | Description | +|----------|------|----------|-------------| +| `--mount` | string (repeatable) | No | Mount specification in format `:`. Creates a `container-mount` child resource linking the container to the mounted resource or host path. | +| `--clone-into` | string (repeatable) | No | Clone specification in format `:`. On activation, clones the referenced `git` resource into the container at the specified path. | + +These arguments are **inherited** by all subtypes of `container-instance` (including `devcontainer-instance`) via the resource type inheritance mechanism (ADR-042). However, `devcontainer-instance` typically does not need `--mount` because the workspace mount is derived from `devcontainer.json`. + +### Sandbox Coordination + +#### Devcontainer Sandbox Domain + +A `devcontainer-instance` creates a sandbox domain (inherited from `container-instance`). The sandbox strategy is `snapshot` — container commit/checkpoint for rollback. + +When a devcontainer bind-mounts the host workspace (the default devcontainer behavior), the mounted files exist in **two sandbox domains simultaneously**: + +1. The `git-checkout` sandbox domain (using `git_worktree` strategy) +2. The `devcontainer-instance` sandbox domain (using `snapshot` strategy) + +This is the cross-mechanism coordination scenario defined in ADR-038. The coherence property for devcontainer workspace bind mounts is `transparent` — changes made inside the container are immediately visible on the host and vice versa, because they share the same underlying storage. + +For devcontainers that use volume mounts instead of bind mounts (configurable in `devcontainer.json` via `workspaceMount`), the coherence is `independent` — the container has its own copy of the workspace. + +#### Checkpoint Behavior + +During plan execution inside a devcontainer: + +1. **File-level checkpoints** are handled by the `git_worktree` sandbox on the host side (since bind mounts are transparent). +2. **Container-level checkpoints** (environment state, installed packages, running processes) are handled by the `snapshot` sandbox on the container side. +3. **Rollback** restores both: git worktree reset for files, container snapshot restore for environment state. + +## Constraints + +- Devcontainer auto-discovery only triggers when both `.devcontainer/` directory AND `devcontainer.json` file exist. A `.devcontainer/` directory without a `devcontainer.json` is ignored. +- The `devcontainer` CLI is an optional dependency. When not installed, the handler falls back to direct Docker/Podman commands, which may not support all devcontainer features (e.g., features, lifecycle scripts). +- Devcontainer build may be slow (pulling images, installing features). The lazy activation model ensures this cost is paid only when needed, but the first plan execution in a devcontainer will have higher latency. +- `--clone-into` requires the referenced resource to be of type `git` or a subtype thereof. Non-git resources cannot be cloned. +- `--mount` with a resource reference requires the resource to have a filesystem representation (a `git-checkout` with an `fs-directory` child, or a standalone `fs-directory`/`fs-mount`). +- Execution environment precedence rules must be deterministic — when multiple devcontainers exist in the hierarchy, the nearest-ancestor rule provides a single unambiguous answer. +- Container-project association is always through resource linking (`agents project link-resource`). There are no container-specific flags on `agents project create`. + +## Consequences + +### Positive + +- Devcontainer configurations are automatically detected and leveraged, giving agents access to reproducible, fully configured development environments without manual setup. +- Three clear association patterns (auto-detect, explicit mount, clone-into) cover the full range of container usage scenarios from local development to cloud CI. +- Execution environment routing with `fallback`/`override` priority gives users fine-grained control while preserving sensible defaults. +- The nearest-ancestor devcontainer resolution supports monorepos with per-service container configurations. +- Lazy activation avoids unnecessary container builds for plans that don't need container execution. +- Resource type inheritance (ADR-042) ensures tools bound to `container-instance` work seamlessly with `devcontainer-instance`. + +### Negative + +- Devcontainer.json parsing adds a dependency on understanding the devcontainer specification, which continues to evolve. +- The execution environment precedence chain (6 levels) adds complexity to the system's routing logic. +- Multi-devcontainer monorepos with nested configurations create complex resolution scenarios that may be surprising to users. +- Container build failures during lazy activation cause plan execution failures at runtime rather than at registration time. + +### Risks + +- The devcontainer specification may introduce breaking changes in future versions. Mitigated by parsing only well-established fields and gracefully ignoring unknown fields. +- Container engine version mismatches (e.g., devcontainer.json targeting Docker features not available in Podman) may cause activation failures. Mitigated by descriptive error messages and engine compatibility checking during the build phase. +- Users may be surprised by automatic devcontainer detection if they have `.devcontainer/` directories they do not intend to use with CleverAgents. Mitigated by the `priority: override` mechanism and by making devcontainer detection configurable (can be disabled via project context settings). + +## Alternatives Considered + +**Devcontainer as a separate top-level resource type (not inheriting from container-instance)** — Would require duplicating all container-instance fields and capabilities. Tools bound to container-instance would not work with the devcontainer type without explicit support. Rejected because it violates DRY and breaks the polymorphism that type inheritance provides. + +**Container association via new CLI flags on `agents project create`** — Adding `--container` and `--mount-path` flags to project creation. Rejected because the existing resource-centric model (register resources independently, link to projects) is more flexible, avoids special-casing containers in the project creation flow, and is consistent with how all other resources are associated with projects. + +**Eager devcontainer activation (build on discovery)** — Building the container immediately when `.devcontainer/` is discovered during resource registration. Rejected because it adds significant latency to resource registration, builds containers for plans that may never need them, and is inconsistent with the system's lazy sandboxing philosophy. + +**Implicit execution environment routing (always prefer container)** — When a project has a container, always execute tools inside it. Rejected because projects may have multiple containers for different purposes (dev, test, deploy), and the user must be able to control which container is used and when. + +## Compliance + +- **Devcontainer detection tests**: `git-checkout` and `fs-directory` auto-discovery correctly detect `.devcontainer/devcontainer.json` and create `devcontainer-instance` resources. +- **Lazy activation tests**: Devcontainer resources remain in `discovered` state until a plan requires execution, then transition through `building` → `active`. +- **Devcontainer.json parsing tests**: All documented devcontainer.json fields are correctly mapped to resource properties. +- **Mount pattern tests**: `--mount` with resource references and raw paths correctly creates `container-mount` children with appropriate DAG links. +- **Clone pattern tests**: `--clone-into` correctly records the clone specification and triggers cloning on activation. +- **Execution environment routing tests**: The full 6-level precedence chain resolves correctly in all combinations of project/plan/resource settings. +- **Nearest-ancestor tests**: Multi-devcontainer hierarchies resolve to the correct nearest devcontainer for each file path. +- **Priority override tests**: `priority: override` correctly overrides auto-detected devcontainers; `priority: fallback` correctly defers to them. +- **Sandbox coordination tests**: Bind-mounted devcontainer files have `transparent` coherence; volume-mounted files have `independent` coherence. +- **Type inheritance tests**: `devcontainer-instance` is correctly recognized as a subtype of `container-instance` by tool bindings, auto-discovery, and DAG queries. + +## Related ADRs + +| ADR | Title | Relationship | +|-----|-------|-------------| +| [ADR-008](ADR-008-resource-system.md) | Resource System | Devcontainer-instance extends the built-in resource type registry | +| [ADR-009](ADR-009-project-model.md) | Project Model | Execution environment routing adds configuration to the project model | +| [ADR-015](ADR-015-sandbox-and-checkpoint.md) | Sandbox and Checkpoint | Devcontainer sandbox coordination builds on the sandbox model | +| [ADR-036](ADR-036-resource-dag-operational-semantics.md) | Resource DAG Operational Semantics | Devcontainer instances create new sandbox domains in the DAG | +| [ADR-038](ADR-038-cross-mechanism-sandbox-coordination.md) | Cross-Mechanism Sandbox Coordination | Devcontainer bind mounts are a key use case for cross-mechanism coordination | +| [ADR-039](ADR-039-container-resource-types.md) | Container and Execution Environment Resource Types | Devcontainer-instance inherits from container-instance defined here | +| [ADR-042](ADR-042-resource-type-inheritance.md) | Resource Type Inheritance | Provides the inheritance mechanism that devcontainer-instance uses | + +## Acceptance + +### Votes For + +| Voter | Comment | +|-------|---------| +| Jeffrey Phillips Freeman | Auto-detected devcontainers with lazy activation and precedence-based routing enable seamless container integration without disrupting existing workflows | + +**Total: 1** + +### Votes Against + +| Voter | Comment | +|-------|---------| + +**Total: 0** + +### Abstentions + +| Voter | Comment | +|-------|---------| + +**Total: 0** diff --git a/docs/adr/index.md b/docs/adr/index.md index 3ed2fdb57..00f5c2cc7 100644 --- a/docs/adr/index.md +++ b/docs/adr/index.md @@ -104,6 +104,10 @@ These ADRs govern cross-cutting behavioral systems and infrastructure concerns. | [ADR-041](ADR-041-safety-profile-extraction.md) | Safety Profile Extraction | Safety constraints extracted into a composed SafetyProfile sub-model within AutomationProfile. | | [ADR-019](ADR-019-storage-and-persistence.md) | Storage and Persistence | SQLite/SQLAlchemy with repository pattern, Unit of Work, and Alembic migrations. | | [ADR-020](ADR-020-session-model.md) | Session Model | Conversation threads with orchestrator binding and session-scoped context. | +| [ADR-036](ADR-036-resource-dag-operational-semantics.md) | Resource DAG Operational Semantics | Ten operational purposes of the resource DAG with sandbox boundary algebra, checkpoint boundaries, and dependency ordering. | +| [ADR-037](ADR-037-tool-reachability-and-access-projection.md) | Tool Reachability and Access Projection | Forward, inverse, and cross-equivalence reachability with AccessProjection protocol and read/write routing. | +| [ADR-038](ADR-038-cross-mechanism-sandbox-coordination.md) | Cross-Mechanism Sandbox Coordination | Coherence properties, write-then-sync protocol, conflict resolution, and lazy virtual node materialization. | +| [ADR-042](ADR-042-resource-type-inheritance.md) | Resource Type Inheritance | Single-inheritance `inherits` field on resource type definitions with polymorphic tool binding, auto-discovery, and DAG queries. | ### Tier 4 — Integration and Operations @@ -123,10 +127,13 @@ These ADRs address external integrations, operational interfaces, and deployment | [ADR-030](ADR-030-skill-abstraction-definition.md) | Skill Abstraction Definition | Canonical definition of a skill as a composable collection of tools from four sources (MCP, Agent Skills, built-in, custom). | | [ADR-031](ADR-031-actor-abstraction-definition.md) | Actor Abstraction Definition | Canonical definition of an actor as anything conversational — single LLM, composed graph, or hierarchical orchestrator. | | [ADR-032](ADR-032-jinja2-yaml-template-preprocessing.md) | Jinja2 YAML Template Preprocessing | Two-phase YAML processing: sandboxed Jinja2 template rendering followed by environment variable interpolation with type coercion. | +| [ADR-039](ADR-039-container-resource-types.md) | Container and Execution Environment Resource Types | Seven engine-agnostic container resource types plus executable discovery for tool/LSP dependency resolution. | +| [ADR-040](ADR-040-lsp-resource-types.md) | LSP Resource Types | Three LSP resource types (lsp-server, lsp-workspace, lsp-document) for language intelligence integration. | +| [ADR-043](ADR-043-devcontainer-integration.md) | Devcontainer Integration and Container-Project Association | Auto-detected devcontainer-instance subtype, container-project association patterns, and execution environment routing with precedence. | ## Creating a New ADR -1. Assign the next sequential number (e.g., `ADR-036`). +1. Assign the next sequential number (e.g., `ADR-044`). 2. Create a file named `ADR-0NN-.md` in this directory. 3. Copy the template structure from any existing ADR. 4. Fill in the metadata table with `Status: Proposed`, the current date, author(s), and leave approver(s) blank. diff --git a/docs/specification.md b/docs/specification.md index a23ebdc32..0927b6d00 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -106,7 +106,16 @@ The following standards are integrated into the architecture: : A ULID-identified entity registered in the Resource Registry representing anything readable, writable, or queryable (git repos, filesystems, databases, etc.). Classified as physical or virtual, typed by a Resource Type, and organized in a DAG via parent/child links. Resource Type - : A schema-level definition constraining a category of resources. Specifies accepted CLI arguments, physical/virtual classification, permitted parent/child type relationships, auto-discovery rules, sandbox strategy, and handler implementation. Built-in types (e.g., `git-checkout`, `fs-mount`) are unnamespaced; custom types are namespaced as `[[server:]namespace/]name`. + : A schema-level definition constraining a category of resources. Specifies accepted CLI arguments, physical/virtual classification, permitted parent/child type relationships, auto-discovery rules, sandbox strategy, and handler implementation. Built-in types (e.g., `git-checkout`, `fs-mount`) are unnamespaced; custom types are namespaced as `[[server:]namespace/]name`. Resource types support single inheritance via the `inherits` field — see [ADR-042](adr/ADR-042-resource-type-inheritance.md). + + Resource Type Inheritance + : A mechanism allowing one resource type to inherit properties, capabilities, child types, sandbox strategy, and handler behavior from another type via the `inherits` field in the type definition. Subtypes can selectively override or extend any inherited field. Tools bound to a parent type automatically work with all subtypes (polymorphic matching). Auto-discovery child type matching and DAG queries are also polymorphic. Single inheritance only; maximum chain depth of 5 levels. See [ADR-042](adr/ADR-042-resource-type-inheritance.md). + + Devcontainer + : A container execution environment defined by a `.devcontainer/devcontainer.json` configuration file per the [Development Containers Specification](https://containers.dev). Auto-discovered when a `git-checkout` or `fs-directory` resource contains a `.devcontainer/` directory. Represented as a `devcontainer-instance` resource type that inherits from `container-instance`. Uses lazy activation — the container is only built when first needed by a plan. See [ADR-043](adr/ADR-043-devcontainer-integration.md). + + Execution Environment + : The runtime context in which tools execute — either the host system or a specific container. Configurable at project scope (`execution_environment` preference), plan scope (`--execution-environment` flag), and resource scope (auto-detected devcontainers). Precedence resolution determines which environment is used when multiple are configured, with `priority: override` forcing a specific container and `priority: fallback` deferring to auto-detected devcontainers. See [ADR-043](adr/ADR-043-devcontainer-integration.md). Physical Resource : A resource bound to a concrete, located artifact — a specific file at a specific path, a specific repo at a specific URL. Directly readable and writable by tools. Two physical resources with identical content remain distinct instances. @@ -245,6 +254,8 @@ The following standards are integrated into the architecture: [--skeleton-ratio <FLOAT>] [--temporal-scope (current|recent|all)] [--auto-refresh|--no-auto-refresh] + [--execution-environment <RESOURCE_NAME>] + [--execution-env-priority (fallback|override)] [--clear] <PROJECT> agents project context show [--view (strategize|execute|apply|default)] <PROJECT> @@ -313,6 +324,8 @@ The following standards are integrated into the architecture: [--execution-actor <EXEC_ACTOR>] [--estimation-actor <EST_ACTOR>] [--invariant-actor <INV_ACTOR>] + [--execution-environment <RESOURCE_NAME>] + [--execution-env-priority (fallback|override)] [--arg/-a name=value]... <ACTION> <PROJECT>... agents plan execute <PLAN_ID> @@ -3743,6 +3756,8 @@ Manage ACMS context policies for the hot/warm/cold tiers, per-view context selec [--skeleton-ratio <FLOAT>] [--temporal-scope (current|recent|all)] [--auto-refresh|--no-auto-refresh] + [--execution-environment <RESOURCE_NAME>] + [--execution-env-priority (fallback|override)] [--clear] <PROJECT> **Purpose** @@ -3771,6 +3786,8 @@ Set the context policy for a project and (optionally) a specific view. - `--skeleton-ratio FLOAT`: Fraction of context budget reserved for inherited plan skeleton. Range: 0.0–1.0. Default: `0.15`. - `--temporal-scope current|recent|all`: Temporal scope for UKO node resolution. Default: `current`. - `--auto-refresh/--no-auto-refresh`: Enable or disable automatic context re-assembly on budget changes. +- `--execution-environment RESOURCE_NAME`: Name of a `container-instance` or `devcontainer-instance` resource to use as the default execution environment for this project. When set, tool invocations during plan execution are routed to this container unless overridden at the plan level. See [ADR-043 §3.3](adr/ADR-043-devcontainer-integration.md). +- `--execution-env-priority fallback|override`: Priority semantics for the project-level execution environment. `fallback` (default): defers to auto-detected devcontainers when present; used only when no closer-scoped environment exists. `override`: always uses the specified environment, bypassing devcontainer auto-detection. See §Execution Environment Routing. - `--clear`: Clear the policy for the selected view. **Examples** @@ -10501,6 +10518,8 @@ Show detailed information about a resource type, including its full schema. | `fs-mount` | Yes | Filesystem mount point | | `fs-directory` | Virtual | Directory within a mount | | `fs-file` | Virtual | Individual file reference | + | `container-instance` | Yes | Container instance ([ADR-039](adr/ADR-039-container-resource-types.md)) | + | `devcontainer-instance` | Yes | Devcontainer instance ([ADR-043](adr/ADR-043-devcontainer-integration.md)) | | Custom types | Varies | Registered via `agents resource type add` | ##### agents resource add @@ -10518,6 +10537,11 @@ Register a new resource. Every resource receives a system-assigned ULID. User-ad Type-specific flags depend on the resource type. See `agents resource type show ` for available arguments. +**Container-specific flags** (for `container-instance` and `devcontainer-instance` types): + +- `--mount RESOURCE_OR_PATH:CONTAINER_PATH`: Mount a resource or host path into the container (repeatable). Accepts either a resource reference (`local/api-repo:/workspace`) or a raw host path (`/home/user/projects/api:/workspace`). See [ADR-043 §3.2](adr/ADR-043-devcontainer-integration.md). +- `--clone-into REPO_URL:CONTAINER_PATH`: Clone a remote repository into the container at the specified path. The clone happens lazily on first container start. + The command returns the name and ULID of the newly created resource. Auto-discovered child resources are created automatically with ULIDs only (no names). **Examples** @@ -10590,6 +10614,75 @@ The command returns the name and ULID of the newly created resource. Auto-discov ✓ OK Resource registered (31 child resources discovered) +

+    $ agents resource add container-instance local/api-dev --mount local/api-repo:/workspace --mount /home/user/.config/nvim:/home/dev/.config/nvim
+
+    ╭─ Resource ──────────────────────────────────────╮
+    │ Name: local/api-dev                             │
+    │ ID: 01HXR3C4D5E6F7G8H9J0K1L2M3                  │
+    │ Type: container-instance                       │
+    │ Physical/Virtual: physical                      │
+    │ State: created (not started)                    │
+    │ Created: 2026-02-09 10:25                       │
+    ╰─────────────────────────────────────────────────╯
+
+    ╭─ Mounts ──────────────────────────────────────────────────────────╮
+    │ Source                            Container Path   Kind           │
+    │ ────────────────────────────────  ───────────────  ─────────    │
+    │ local/api-repo                    /workspace       resource-ref  │
+    │ /home/user/.config/nvim           /home/dev/…      host-path     │
+    ╰───────────────────────────────────────────────────────────────────╯
+
+    ✓ OK Container resource registered (container will start on first access)
+    
+ +

+    $ agents resource add container-instance cloud/ci-runner --clone-into https://github.com/acme/api.git:/workspace
+
+    ╭─ Resource ──────────────────────────────────────╮
+    │ Name: cloud/ci-runner                           │
+    │ ID: 01HXR4D5E6F7G8H9J0K1L2M3N4                  │
+    │ Type: container-instance                       │
+    │ Physical/Virtual: physical                      │
+    │ State: created (not started)                    │
+    │ Clone: https://github.com/acme/api.git          │
+    │ Clone Target: /workspace                         │
+    │ Created: 2026-02-09 10:27                       │
+    ╰─────────────────────────────────────────────────╯
+
+    ✓ OK Container resource registered (repo will be cloned on first start)
+    
+ +

+    $ agents resource add git-checkout local/webapp --path /home/user/projects/webapp
+
+    ╭─ Resource ──────────────────────────────────────────╮
+    │ Name: local/webapp                                  │
+    │ ID: 01HXR5E6F7G8H9J0K1L2M3N4O5                      │
+    │ Type: git-checkout                                  │
+    │ Physical/Virtual: physical                          │
+    │ Path: /home/user/projects/webapp                   │
+    │ Branch: main                                        │
+    │ Created: 2026-02-09 10:30                           │
+    ╰────────────────────────────────────────────────────╯
+
+    ╭─ Auto-discovered Children ─────────────────────────────────────────╮
+    │ ID               Type                    Status                  │
+    │ ───────────────  ──────────────────────  ─────────────────       │
+    │ 01HXR5E6F7G9…   git                     created                  │
+    │ 01HXR5E6F7GA…   devcontainer-instance   detected (not built)    │
+    │ 01HXR5E6F7GB…   fs-directory            created                  │
+    │   + 52 git-commit resources                                        │
+    │   + 189 git-tree-entry resources                                   │
+    ╰────────────────────────────────────────────────────────────────────╯
+
+    ⚠ Devcontainer detected at .devcontainer/devcontainer.json
+      Container will be built lazily on first access.
+      Use agents resource show 01HXR5E6F7GA… to inspect.
+
+    ✓ OK Resource registered (245 child resources discovered)
+    
+ === "Plain" ``` @@ -10648,6 +10741,64 @@ The command returns the name and ULID of the newly created resource. Auto-discov Sandbox Strategy: copy_on_write [OK] Resource registered (31 child resources discovered) + + $ agents resource add container-instance local/api-dev --mount local/api-repo:/workspace --mount /home/user/.config/nvim:/home/dev/.config/nvim + + Resource + Name: local/api-dev + ID: 01HXR3C4D5E6F7G8H9J0K1L2M3 + Type: container-instance + Physical/Virtual: physical + State: created (not started) + Created: 2026-02-09 10:25 + + Mounts + Source Container Path Kind + -------------------------------- --------------- --------- + local/api-repo /workspace resource-ref + /home/user/.config/nvim /home/dev/... host-path + + [OK] Container resource registered (container will start on first access) + + $ agents resource add container-instance cloud/ci-runner --clone-into https://github.com/acme/api.git:/workspace + + Resource + Name: cloud/ci-runner + ID: 01HXR4D5E6F7G8H9J0K1L2M3N4 + Type: container-instance + Physical/Virtual: physical + State: created (not started) + Clone: https://github.com/acme/api.git + Clone Target: /workspace + Created: 2026-02-09 10:27 + + [OK] Container resource registered (repo will be cloned on first start) + + $ agents resource add git-checkout local/webapp --path /home/user/projects/webapp + + Resource + Name: local/webapp + ID: 01HXR5E6F7G8H9J0K1L2M3N4O5 + Type: git-checkout + Physical/Virtual: physical + Path: /home/user/projects/webapp + Branch: main + Created: 2026-02-09 10:30 + + Auto-discovered Children + ID Type Status + --------------- ---------------------- ----------------- + 01HXR5E6F7G9.. git created + 01HXR5E6F7GA.. devcontainer-instance detected (not built) + 01HXR5E6F7GB.. fs-directory created + + 52 git-commit resources + + 189 git-tree-entry resources + + [WARN] Devcontainer detected at .devcontainer/devcontainer.json + Container will be built lazily on first access. + Use agents resource show 01HXR5E6F7GA… to inspect. + + [OK] Resource registered (245 child resources discovered) ``` === "JSON" @@ -12208,6 +12359,8 @@ Filtering by project with `--project`: [--execution-actor <EXEC_ACTOR>] [--estimation-actor <EST_ACTOR>] [--invariant-actor <INV_ACTOR>] + [--execution-environment <RESOURCE_NAME>] + [--execution-env-priority (fallback|override)] [--arg/-a name=value]... <ACTION> <PROJECT>... @@ -12225,6 +12378,8 @@ Apply an action to one or more projects and start the Strategize phase. - `--estimation-actor ACTOR`: Override the action's estimation actor for this plan. - `--invariant-actor ACTOR`: Override the action's Invariant Reconciliation Actor for this plan. - `--invariant TEXT`: Invariant to attach to the created plan (repeatable). These are added as plan-level invariants in addition to any invariants inherited from the action, project, or global scope. +- `--execution-environment RESOURCE_NAME`: Name of a `container-instance` or `devcontainer-instance` resource to use as the execution environment for this plan. Overrides or supplements the project-level `execution_environment` setting depending on `--execution-env-priority`. See [ADR-043 §3.3](adr/ADR-043-devcontainer-integration.md). +- `--execution-env-priority fallback|override`: Priority semantics for the plan-level execution environment. `fallback` (default): defers to auto-detected devcontainers or project-level overrides. `override`: always uses the specified environment, bypassing all other resolution. See §Execution Environment Routing. All actor arguments (`--strategy-actor`, `--execution-actor`, `--estimation-actor`, `--invariant-actor`) are optional overrides. When provided, they replace whatever was set when creating the action. When omitted, the action's configured actors are used. @@ -19047,6 +19202,70 @@ Execution should be treated like a transactional pipeline: This is explicitly motivated by "partial failure leaves codebase inconsistent" and the need for transaction rollback. +#### Execution Environment Routing + +!!! adr "Architecture Decision" + Execution environment routing, devcontainer integration, and the 6-level precedence chain are defined in [ADR-043: Devcontainer Integration](adr/ADR-043-devcontainer-integration.md). Container resource types are defined in [ADR-039: Container Resource Types](adr/ADR-039-container-resource-types.md). + +When a plan enters the **Execute** phase, the runtime must determine _where_ each tool invocation runs: on the host, or inside a container. This decision is called **execution environment routing**. The router evaluates a 6-level precedence chain and selects the first matching environment. + +**Precedence Chain (highest → lowest):** + +| Priority | Source | Condition | +| :------: | :----- | :-------- | +| 1 | Plan-level `execution_environment` with `priority: override` | Always wins when set. Configured via `agents plan use --execution-environment … --execution-env-priority override`. | +| 2 | Project-level `execution_environment` with `priority: override` | Wins unless a plan-level override exists. Configured via `agents project context set --execution-environment … --execution-env-priority override`. | +| 3 | Nearest-ancestor devcontainer | Auto-detected `devcontainer-instance` resource that is a child (or descendant) of a resource linked to the project. Lazy-built on first access. | +| 4 | Plan-level `execution_environment` with `priority: fallback` | Used only when no devcontainer is detected and no override exists above. | +| 5 | Project-level `execution_environment` with `priority: fallback` | Used only when no closer-scoped environment exists. | +| 6 | Host | The local operating system. Default when no container environment is configured or detected. | + +**Routing Algorithm:** + +``` +function resolve_execution_environment(plan, project): + # Level 1: plan override + if plan.execution_environment AND plan.execution_env_priority == "override": + return plan.execution_environment + + # Level 2: project override + if project.execution_environment AND project.execution_env_priority == "override": + return project.execution_environment + + # Level 3: nearest-ancestor devcontainer + for resource in project.linked_resources (ordered by DAG depth, shallowest first): + devcontainer = find_child_of_type(resource, "devcontainer-instance") + if devcontainer: + if devcontainer.state == "detected": + build_and_start(devcontainer) # lazy activation + return devcontainer + + # Level 4: plan fallback + if plan.execution_environment AND plan.execution_env_priority == "fallback": + return plan.execution_environment + + # Level 5: project fallback + if project.execution_environment AND project.execution_env_priority == "fallback": + return project.execution_environment + + # Level 6: host + return HOST_ENVIRONMENT +``` + +**Tool-Level Environment Preferences:** + +Individual tools may declare environment preferences via the `environment` field in their capability metadata (see §Tool Capability Metadata): + +- `environment.required`: Tool _must_ run in this environment type (`container` or `host`). If the resolved environment doesn't match, the tool invocation fails with an error rather than silently running in the wrong environment. +- `environment.preferred`: Tool _prefers_ this environment type but will run wherever the router places it. +- `environment.specific`: Tool targets a specific named resource (e.g., `local/api-dev`). If the named resource is available and running, the tool is routed there regardless of the general precedence chain. + +When a tool declares `environment.required: container` but the router resolves to `host`, the runtime MUST raise an error. The operator can then either: (a) add a container resource to the project, or (b) change the tool's environment requirement. + +**Lazy Activation:** + +Devcontainers detected during auto-discovery are created in `detected (not built)` state. They are built and started _only_ when the execution environment router first selects them. This avoids unnecessary container builds when the user never executes a plan that needs container isolation. Once built, the container remains available for subsequent plan executions until explicitly stopped or the resource is removed. + #### Tool-Based Resource Modification (Modern Architecture) !!! adr "Architecture Decision" @@ -19507,6 +19726,28 @@ Project-level defaults: * preferred chunking/summarization policy (even if evolving) * context retention policy +##### 4) Execution Environment + +!!! adr "Architecture Decision" + Execution environment routing is defined in [ADR-043: Devcontainer Integration and Container-Project Association](adr/ADR-043-devcontainer-integration.md). + +Projects can configure a default execution environment — a container in which tools execute instead of on the host. This is set via `agents project context set`: + +

+execution_environment:
+  default: local/dev-container        # Resource name of the default container
+  priority: fallback                 # fallback | override
+
+ +**Priority semantics:** + +* `fallback` (default): Use this execution environment only when no devcontainer is auto-detected for the current resource or its ancestors. If a resource (or ancestor) has a `.devcontainer/`, the devcontainer wins. +* `override`: Always use this execution environment, ignoring any auto-detected devcontainers. + +When a project has multiple containers linked as resources, the `execution_environment.default` field specifies which one to use. Without this setting, the system relies on auto-detected devcontainers or falls back to host execution. + +Plans can override the project-level execution environment via `--execution-environment` on `agents plan use` (see [Execution Environment Routing](#execution-environment-routing)). + #### Multi-Project Operations A single plan may target multiple projects (e.g., updating shared schemas across services). This is considered a key UX advantage over "run in one directory" systems. Because resources are independently registered and can be linked to multiple projects, shared resources across projects are a natural part of the architecture. @@ -21649,7 +21890,10 @@ MCP's metadata is not sufficient (read-only/idempotent is not enough; write scop write_scope: # What the tool is allowed to mutate - file_paths: ["src/**", "tests/**"] # Path patterns within bound resources - resource_slots: ["repo", "db"] # Resource slot names (from resource bindings) - - environment: container | host + - environment: # Execution environment compatibility + required: container | host | any # Where the tool CAN run (default: any) + preferred: container | host # Where the tool PREFERS to run (optional) + specific: <resource-name> # A specific container required (optional) idempotent: bool # Whether repeated calls produce same result checkpointable: bool # Whether tool supports checkpoint/rollback checkpoint_scope: str # What can be rolled back (file, transaction, commit, snapshot) @@ -23164,10 +23408,55 @@ A **resource type** is a schema-level definition that constrains a category of r * **User addable**: Whether users can create instances of this type directly via `agents resource add `. Types with `user_addable: false` are only generated as auto-discovered children of other resources. * **Sandbox strategy**: The default sandboxing approach for instances of this type. * **Handler**: The resource handler implementation that provides read/write/sandbox/checkpoint operations. +* **Inheritance**: Optionally, a parent type from which this type inherits all of the above via the `inherits` field. + +##### Resource Type Inheritance + +!!! adr "Architecture Decision" + Resource type inheritance is defined in [ADR-042: Resource Type Inheritance](adr/ADR-042-resource-type-inheritance.md). + +Resource types support **single-inheritance specialization** via an `inherits` field. A subtype inherits all properties, capabilities, child type constraints, sandbox strategy, and handler behavior from its parent type, and can selectively override or extend any inherited field. + +**Core polymorphism guarantee:** Tools bound to a parent type automatically work with all subtypes. Auto-discovery child type matching and DAG queries that reference a parent type automatically include subtypes. + +**Field resolution:** When the system resolves a field for a resource type, it walks the inheritance chain from the most specific type to the root. If the subtype declares the field, the subtype's value is used (override). If omitted, the parent's value is inherited. + +**Collection field merging:** Fields that are collections (`cli_args`, `child_types`, `parent_types`) use additive merging by default — the subtype's entries are appended to the parent's, with same-name entries replaced. A subtype can use `_replace: true` to replace a collection entirely. + +**Inheritance rules:** + +1. Single inheritance only (no diamond). +2. Maximum chain depth of 5 levels. +3. No circular inheritance (validated at registration time). +4. Built-in types may not inherit from custom (namespaced) types. +5. Removing a parent type is prohibited while subtypes exist. + +Example: + +

+# A subtype that inherits from container-instance
+name: devcontainer-instance
+inherits: container-instance
+description: "A container provisioned from a devcontainer.json configuration"
+
+# Only fields that differ from or extend container-instance need to be declared.
+# All other fields (capabilities, sandbox_strategy, child_types, etc.) are inherited.
+cli_args:
+  # Inherited args from container-instance remain available.
+  # Additional args specific to devcontainer:
+  - name: config-path
+    type: path
+    required: false
+    description: "Path to .devcontainer/devcontainer.json or .devcontainer/ directory"
+
+handler:
+  class: DevcontainerInstanceHandler
+  module: cleveragents.resources.handlers.devcontainer
+
##### Built-in Resource Types -Built-in types are organized into three layers: **git** (version control structure), **git-checkout** (a composition that bridges git metadata with a local directory), and **filesystem** (physical files on disk). Virtual types link equivalent physical resources across these layers through content/identity matching. There are 24 built-in types total (15 physical + 9 virtual), of which 4 are user-addable (`git-checkout`, `git`, `fs-mount`, `fs-directory`). +Built-in types are organized into four layers: **git** (version control structure), **git-checkout** (a composition that bridges git metadata with a local directory), **filesystem** (physical files on disk), and **container** (containerized execution environments). Virtual types link equivalent physical resources across these layers through content/identity matching. There are 34 built-in types total (24 physical + 1 subtype + 9 virtual), of which 4 are user-addable as top-level resources (`git-checkout`, `git`, `fs-mount`, `fs-directory`), plus container types documented in [ADR-039](adr/ADR-039-container-resource-types.md) and the `devcontainer-instance` subtype documented in [ADR-043](adr/ADR-043-devcontainer-integration.md). Each table below includes the full parent/child relationship constraints. **Allowed Parents** lists what types may be a parent of this type, with cardinality (how many parents of that type are allowed) and whether the relationship is required or optional. **Allowed Children** lists what types may be children, with cardinality and whether they are required or optional. `0..*` means zero or more, `1` means exactly one (required), `0..1` means zero or one (optional). @@ -23481,6 +23770,28 @@ agents resource add fs-directory local/acme-deploy \ @endwbs ``` +When the `git-checkout` contains a `.devcontainer/devcontainer.json`, an additional `devcontainer-instance` child is auto-discovered: + +```kroki-plantuml +@startwbs +* local/acme-app\n(git-checkout / physical) +** local/acme-app:repo\n(git / physical) +*** (branches, commits, trees, ...) +** local/acme-app:worktree\n(fs-directory / physical) +*** worktree:.devcontainer/\n(fs-directory) +**** worktree:.devcontainer/devcontainer.json\n(fs-file) +*** worktree:src/\n(fs-directory) +**** worktree:src/app.ts\n(fs-file) +*** worktree:package.json\n(fs-file) +** local/acme-app:devcontainer\n(devcontainer-instance / discovered) +*** [container-mount — pending activation] +*** [container-exec-env — pending activation] +*** [container-port — pending activation] +@endwbs +``` + +The `devcontainer-instance` is in `discovered` state — its container-mount, container-exec-env, and container-port children are only created when the container is activated during plan execution. This is consistent with lazy sandboxing: no container is built until a tool actually needs to execute inside it. + The `git-checkout` cleanly separates two concerns: the `git` child contains version control structure (remotes, branches, tags, stashes, submodules, commits, trees, and tree entries — git's full object model), while the `fs-directory` child is the worktree root directory containing the actual files on disk. Note how git's internal structure is fully modeled: `git-commit` → `git-tree` (root tree object) → `git-tree-entry` (blobs) and nested `git-tree` (subtrees). The worktree root is a directory (`fs-directory`), not a mount point — `git-checkout` does not own an `fs-mount` resource because a git checkout's worktree is just a directory on an existing filesystem. When content matches (as it does for a clean checkout), virtual types link the `fs-file` and `git-tree-entry` resources. **`local/acme-upstream`** (type: `git`, remote URL — NOT checked out) discovers: @@ -24127,6 +24438,8 @@ Common coherence assignments: | Scenario | Coherence | |----------|-----------| | `fs-file` via git-checkout ↔ `fs-file` via container bind mount | `transparent` (same inode through bind mount) | +| `fs-file` via git-checkout ↔ `fs-file` via devcontainer workspace bind mount | `transparent` (default devcontainer behavior: bind mount) | +| `fs-file` via git-checkout ↔ `fs-file` via devcontainer workspace volume mount | `independent` (configurable via `workspaceMount` in devcontainer.json) | | `fs-file` ↔ `lsp-document` | `cached` (LSP buffers file content) | | `fs-file` via git-checkout ↔ `fs-file` via container volume mount | `independent` (separate copy) | | `git-commit` in repo A ↔ `git-commit` in repo B | `independent` (same hash but separate stores) | @@ -24179,7 +24492,7 @@ agents project link-resource local/api-service local/docs /devcontainer.json`. +* **Workspace mount**: The devcontainer's workspace mount defaults to the parent `git-checkout` path (or `fs-directory` path). The `container-mount` relationship is recorded at discovery time but not materialized until activation. +* **Auto-detected execution environment**: The discovered devcontainer automatically becomes the default execution environment for tools operating on the parent resource, subject to execution environment precedence rules (see [Execution Environment Routing](#execution-environment-routing)). + #### Resource Capabilities Each resource declares its capabilities, derived from its resource type: @@ -24316,6 +24643,7 @@ The sandbox strategy is inherited by child resources from their parent unless th | Resource Type | Sandbox Strategy | Rollback Mechanism | |--------------|------------------|-------------------| | `container-instance` | `snapshot` | Container commit/checkpoint | +| `devcontainer-instance` (inherits `container-instance`) | `snapshot` (inherited) | Container commit/checkpoint (inherited) | | `container-volume` | `snapshot` | Volume snapshot | | `container-mount` | (inherits from `container-instance`) | (inherits) | | `container-exec-env` | (inherits from `container-instance`) | (inherits) | @@ -24474,6 +24802,7 @@ Every resource type provides a handler that implements this interface: | `container-runtime` | `ContainerRuntimeHandler` | ✓ | ✗ | ✗ | `none` | | `container-image` | `ContainerImageHandler` | ✓ | ✗ | ✗ | `none` | | `container-instance` | `ContainerInstanceHandler` | ✓ | ✓ | ✓ | `snapshot` | +| `devcontainer-instance` (inherits `container-instance`) | `DevcontainerInstanceHandler` | ✓ | ✓ | ✓ | `snapshot` (inherited) | | `container-mount`, `container-exec-env`, `container-port` | `ContainerChildHandler` | ✓ | varies | ✗ | (inherits) | | `container-volume` | `ContainerVolumeHandler` | ✓ | ✓ | ✓ | `snapshot` | | `container-network` | `ContainerNetworkHandler` | ✓ | ✗ | ✗ | `none` | @@ -24485,7 +24814,7 @@ Every resource type provides a handler that implements this interface: Additional handlers are provided by custom resource types when they are registered. !!! adr "Architecture Decision" - Container resource types are defined in [ADR-039: Container and Execution Environment Resource Types](adr/ADR-039-container-resource-types.md). LSP resource types are defined in [ADR-040: LSP Resource Types](adr/ADR-040-lsp-resource-types.md). + Container resource types are defined in [ADR-039: Container and Execution Environment Resource Types](adr/ADR-039-container-resource-types.md). The `devcontainer-instance` subtype, container-project association patterns, and execution environment routing are defined in [ADR-043: Devcontainer Integration and Container-Project Association](adr/ADR-043-devcontainer-integration.md). Resource type inheritance (the `inherits` mechanism) is defined in [ADR-042: Resource Type Inheritance](adr/ADR-042-resource-type-inheritance.md). LSP resource types are defined in [ADR-040: LSP Resource Types](adr/ADR-040-lsp-resource-types.md). ##### Resource Path Resolution @@ -32373,6 +32702,12 @@ The following annotated YAML provides an easier-to-read overview of the same sch # Virtual: an abstract identity linking equivalent physical resources user_addable: true # Whether users can create instances directly (optional, default: true) +# ─── Type Inheritance ─────────────────────────────────────────────── +inherits: <parent-type-name> # Parent resource type to inherit from (optional) + # Subtypes inherit all fields from the parent type. + # Only fields that differ from or extend the parent need to be declared. + # See ADR-042 for full inheritance semantics. + # ─── CLI Arguments ────────────────────────────────────────────────── # Define the arguments accepted by `agents resource add <type>`. cli_args: @@ -32436,8 +32771,9 @@ The following annotated YAML provides an easier-to-read overview of the same sch | `description` | string | Yes | Human-readable description of what this resource type represents. | | `physical` | boolean | Yes | `true` for physical types (concrete manifestations), `false` for virtual types (abstract identity linking equivalent physical resources). | | `user_addable` | boolean | No | Whether users can create instances directly via `agents resource add `. Default: `true`. When `false`, instances are only created via auto-discovery as children of other resources. | -| `sandbox_strategy` | string | Yes | The sandbox strategy used when executing within this resource type. | -| `handler` | object | Yes | The Python handler class that implements resource operations. | +| `inherits` | string | No | Parent resource type name to inherit from. Subtypes inherit all properties, capabilities, child types, sandbox strategy, and handler behavior from the parent. Only fields that differ from or extend the parent need to be declared. Collection fields (`cli_args`, `child_types`, `parent_types`) use additive merging by default; use `_replace: true` to replace entirely. Single inheritance only; maximum chain depth of 5. See [ADR-042](adr/ADR-042-resource-type-inheritance.md). | +| `sandbox_strategy` | string | Yes (unless inherited) | The sandbox strategy used when executing within this resource type. Inherited from parent type if `inherits` is set and field is omitted. | +| `handler` | object | Yes (unless inherited) | The Python handler class that implements resource operations. Inherited from parent type if `inherits` is set and field is omitted; subtypes typically override with a subclass handler. | **CLI Argument Fields (`cli_args[]`)** @@ -32806,6 +33142,75 @@ A resource type for Docker container registries with full auto-discovery and lif - "**/*-dirty" +**Example 6: Devcontainer Instance Resource Type (Inherited)** + +This example demonstrates the `devcontainer-instance` type, which inherits from `container-instance` via the resource type inheritance mechanism ([ADR-042](adr/ADR-042-resource-type-inheritance.md)). Fields not shown here are inherited from the parent type. Only the additional fields and overrides specific to devcontainer semantics are defined. + +
# devcontainer-instance inherits from container-instance (ADR-039)
+# See ADR-042 for inheritance rules, ADR-043 for devcontainer lifecycle
+
+name: "devcontainer-instance"
+inherits: "container-instance"
+description: "A container instance defined by a devcontainer.json configuration file. Auto-discovered as a child of git-checkout resources containing a .devcontainer/ directory. Supports lazy activation — detected at discovery time but built only on first access."
+physical_virtual: "physical"
+
+fields:
+  # Inherited from container-instance: image, engine, ports, environment, volumes
+  # Additional fields specific to devcontainer semantics:
+  devcontainer_json_path:
+    type: "string"
+    required: true
+    description: "Relative path to devcontainer.json from the parent resource root (e.g., .devcontainer/devcontainer.json)"
+  workspace_folder:
+    type: "string"
+    required: false
+    default: "/workspaces/${localWorkspaceFolderBasename}"
+    description: "Container-side workspace path, parsed from devcontainer.json workspaceFolder field"
+  features:
+    type: "map<string, object>"
+    required: false
+    description: "Dev Container Features to install, parsed from devcontainer.json features field"
+  post_create_command:
+    type: "string | list<string>"
+    required: false
+    description: "Command(s) to run after container creation, from devcontainer.json postCreateCommand"
+  post_start_command:
+    type: "string | list<string>"
+    required: false
+    description: "Command(s) to run after container start, from devcontainer.json postStartCommand"
+  activation_state:
+    type: "enum(detected, building, running, stopped, failed)"
+    required: true
+    default: "detected"
+    description: "Lifecycle state. Starts as 'detected' (lazy); transitions to 'building' then 'running' on first access."
+
+handler: "DevcontainerInstanceHandler"
+
+sandbox_strategy: "container_snapshot"
+# Overrides the parent's sandbox_strategy; devcontainers use container snapshots
+# for checkpoint/rollback rather than the generic container strategy.
+
+capabilities:
+  readable: true
+  writable: true
+  sandboxable: true
+  checkpointable: true
+  executable: true
+
+auto_discovery:
+  enabled: true
+  parent_types:
+    - "git-checkout"
+  detection:
+    scan_paths:
+      - ".devcontainer/devcontainer.json"
+      - ".devcontainer.json"
+    activation: "lazy"
+    # Container is NOT built at discovery time.
+    # State remains "detected" until the execution environment router
+    # selects this devcontainer for tool execution.
+
+ --- ### Context View Configuration @@ -40050,6 +40455,633 @@ On a different developer's machine: ✓ OK Changes applied +### Example 16: Devcontainer-Driven Development + +!!! adr "Related ADRs" + [ADR-043: Devcontainer Integration](adr/ADR-043-devcontainer-integration.md) · [ADR-042: Resource Type Inheritance](adr/ADR-042-resource-type-inheritance.md) · [ADR-039: Container Resource Types](adr/ADR-039-container-resource-types.md) + +**Scenario:** A developer registers a git checkout that contains a `.devcontainer/` directory. The system auto-detects the devcontainer, creates it in `detected` state, then lazily builds and starts it when a plan executes. All tool invocations route to the container via execution environment routing. + +**What this example demonstrates:** + +- Devcontainer auto-discovery during `agents resource add` +- Lazy activation (container not built until first plan execution) +- Execution environment routing selecting the nearest-ancestor devcontainer (precedence level 3) +- Tool invocations running inside the container +- Apply phase writing changes back to host via bind mount + +**Step 1 — Register the git checkout (devcontainer auto-detected):** + +=== "Rich" + +

+    $ agents resource add git-checkout local/webapp --path /home/user/projects/webapp
+
+    ╭─ Resource ──────────────────────────────────────────╮
+    │ Name: local/webapp                                  │
+    │ ID: 01J0A1B2C3D4E5F6G7H8J9K0L1                      │
+    │ Type: git-checkout                                  │
+    │ Path: /home/user/projects/webapp                   │
+    │ Branch: main                                        │
+    ╰────────────────────────────────────────────────────╯
+
+    ╭─ Auto-discovered Children ─────────────────────────────────────────╮
+    │ ID               Type                    Status                  │
+    │ ───────────────  ──────────────────────  ─────────────────       │
+    │ 01J0A1B2C3D5…   git                     created                  │
+    │ 01J0A1B2C3D6…   devcontainer-instance   detected (not built)    │
+    │ 01J0A1B2C3D7…   fs-directory            created                  │
+    │   + 38 git-commit, 205 git-tree-entry, 12 fs-file              │
+    ╰────────────────────────────────────────────────────────────────────╯
+
+    ⚠ Devcontainer detected at .devcontainer/devcontainer.json
+      Container will be built lazily on first access.
+
+    ✓ OK Resource registered (259 child resources discovered)
+    
+ +=== "Plain" + + ``` + $ agents resource add git-checkout local/webapp --path /home/user/projects/webapp + + Resource + Name: local/webapp + ID: 01J0A1B2C3D4E5F6G7H8J9K0L1 + Type: git-checkout + Path: /home/user/projects/webapp + Branch: main + + Auto-discovered Children + ID Type Status + --------------- ---------------------- ----------------- + 01J0A1B2C3D5.. git created + 01J0A1B2C3D6.. devcontainer-instance detected (not built) + 01J0A1B2C3D7.. fs-directory created + + 38 git-commit, 205 git-tree-entry, 12 fs-file + + [WARN] Devcontainer detected at .devcontainer/devcontainer.json + Container will be built lazily on first access. + + [OK] Resource registered (259 child resources discovered) + ``` + +**Step 2 — Create project and link the resource:** + +=== "Rich" + +

+    $ agents project create local/webapp-project
+    ✓ OK Project created: local/webapp-project
+
+    $ agents project link-resource local/webapp-project local/webapp
+    ✓ OK Resource local/webapp linked to project local/webapp-project
+    
+ +=== "Plain" + + ``` + $ agents project create local/webapp-project + [OK] Project created: local/webapp-project + + $ agents project link-resource local/webapp-project local/webapp + [OK] Resource local/webapp linked to project local/webapp-project + ``` + +**Step 3 — Start a plan (devcontainer lazily built):** + +=== "Rich" + +

+    $ agents plan use local/add-dark-mode local/webapp-project \
+      --arg feature=dark-mode --automation-profile supervised
+
+    ╭─ Plan Created ──────────────────────────────────────╮
+    │ Plan ID: 01J0B2C3D4E5F6G7H8J9K0L1M2            │
+    │ Phase: strategize                                 │
+    │ Action: local/add-dark-mode                       │
+    │ Project: local/webapp-project                     │
+    │ Automation: supervised                             │
+    ╰─────────────────────────────────────────────────────╯
+
+    ✓ OK Plan created — strategize phase starting
+    
+ +=== "Plain" + + ``` + $ agents plan use local/add-dark-mode local/webapp-project \ + --arg feature=dark-mode --automation-profile supervised + + Plan Created + Plan ID: 01J0B2C3D4E5F6G7H8J9K0L1M2 + Phase: strategize + Action: local/add-dark-mode + Project: local/webapp-project + Automation: supervised + + [OK] Plan created — strategize phase starting + ``` + +**Step 4 — Execute the plan (devcontainer activation + tool routing):** + +=== "Rich" + +

+    $ agents plan execute 01J0B2C3D4E5F6G7H8J9K0L1M2
+
+    ⏳ Building devcontainer from .devcontainer/devcontainer.json …
+      Image: mcr.microsoft.com/devcontainers/typescript-node:1-20
+      Features: ghcr.io/devcontainers/features/git:1
+      Running postCreateCommand: npm install
+    ✓ Devcontainer ready (01J0A1B2C3D6…) in 42s
+
+    ╭─ Execution Environment ───────────────────────────╮
+    │ Resolved via: nearest-ancestor devcontainer         │
+    │ Resource: 01J0A1B2C3D6… (devcontainer-instance)    │
+    │ Workspace: /workspaces/webapp                      │
+    │ Precedence: level 3 of 6                           │
+    ╰───────────────────────────────────────────────────╯
+
+    ▸ Decision 1/3: Create src/theme/dark-mode.ts
+      → write_file() in container /workspaces/webapp/src/theme/dark-mode.ts
+       Checkpoint cp-001
+
+    ▸ Decision 2/3: Update src/App.tsx to import ThemeProvider
+      → edit_file() in container /workspaces/webapp/src/App.tsx
+       Checkpoint cp-002
+
+    ▸ Decision 3/3: Run tests
+      → run_command() in container npm test
+       All 47 tests passed
+       Checkpoint cp-003
+
+    ╭─ Execution Complete ──────────────────╮
+    │ Decisions: 3/3 succeeded              │
+    │ Checkpoints: 3                         │
+    │ Environment: devcontainer              │
+    │ Duration: 1m 12s (incl. 42s build)    │
+    ╰───────────────────────────────────────╯
+
+    ✓ OK Execution complete — ready for apply
+    
+ +=== "Plain" + + ``` + $ agents plan execute 01J0B2C3D4E5F6G7H8J9K0L1M2 + + [WAIT] Building devcontainer from .devcontainer/devcontainer.json … + Image: mcr.microsoft.com/devcontainers/typescript-node:1-20 + Features: ghcr.io/devcontainers/features/git:1 + Running postCreateCommand: npm install + [OK] Devcontainer ready (01J0A1B2C3D6…) in 42s + + Execution Environment + Resolved via: nearest-ancestor devcontainer + Resource: 01J0A1B2C3D6… (devcontainer-instance) + Workspace: /workspaces/webapp + Precedence: level 3 of 6 + + Decision 1/3: Create src/theme/dark-mode.ts + -> write_file() in container /workspaces/webapp/src/theme/dark-mode.ts + [OK] Checkpoint cp-001 + + Decision 2/3: Update src/App.tsx to import ThemeProvider + -> edit_file() in container /workspaces/webapp/src/App.tsx + [OK] Checkpoint cp-002 + + Decision 3/3: Run tests + -> run_command() in container npm test + [OK] All 47 tests passed + [OK] Checkpoint cp-003 + + Execution Complete + Decisions: 3/3 succeeded + Checkpoints: 3 + Environment: devcontainer + Duration: 1m 12s (incl. 42s build) + + [OK] Execution complete — ready for apply + ``` + +**Step 5 — Apply changes to host:** + +=== "Rich" + +

+    $ agents plan apply --yes 01J0B2C3D4E5F6G7H8J9K0L1M2
+
+    ╭─ Apply ──────────────────────────────────────────╮
+    │ Files modified: 2                                │
+    │   + src/theme/dark-mode.ts (new)                 │
+    │   ~ src/App.tsx (modified)                       │
+    │ Target: /home/user/projects/webapp (host)        │
+    │ Via: bind mount sync from container workspace     │
+    ╰──────────────────────────────────────────────────╯
+
+    ✓ OK Changes applied to host filesystem
+    
+ +=== "Plain" + + ``` + $ agents plan apply --yes 01J0B2C3D4E5F6G7H8J9K0L1M2 + + Apply + Files modified: 2 + + src/theme/dark-mode.ts (new) + ~ src/App.tsx (modified) + Target: /home/user/projects/webapp (host) + Via: bind mount sync from container workspace + + [OK] Changes applied to host filesystem + ``` + +### Example 17: Explicit Container with Directory Mount + +!!! adr "Related ADRs" + [ADR-043: Devcontainer Integration](adr/ADR-043-devcontainer-integration.md) · [ADR-039: Container Resource Types](adr/ADR-039-container-resource-types.md) + +**Scenario:** A team uses a custom container image (not a devcontainer) for development. The operator explicitly creates a `container-instance` resource with `--mount` to bind a local directory into the container, then sets it as the project's execution environment with `override` priority. + +**What this example demonstrates:** + +- Explicit container-instance creation via `agents resource add` with `--mount` flags +- Both resource-reference and raw host-path mount styles +- Project-level `execution_environment` with `priority: override` (precedence level 2) +- Container started on first plan execution, no devcontainer.json needed + +**Step 1 — Register the code repository and container:** + +=== "Rich" + +

+    $ agents resource add git-checkout local/api-repo --path /home/user/projects/api
+    ✓ OK Resource registered: local/api-repo (127 children)
+
+    $ agents resource add container-instance local/api-container \
+      --mount local/api-repo:/workspace \
+      --mount /home/user/.ssh:/home/dev/.ssh:ro
+
+    ╭─ Resource ──────────────────────────────────────╮
+    │ Name: local/api-container                       │
+    │ ID: 01J0C3D4E5F6G7H8J9K0L1M2N3                  │
+    │ Type: container-instance                       │
+    │ State: created (not started)                    │
+    ╰─────────────────────────────────────────────────╯
+
+    ╭─ Mounts ──────────────────────────────────────────────────────────╮
+    │ Source                    Container Path       Kind   Mode  │
+    │ ────────────────────────  ──────────────────   ──────  ────  │
+    │ local/api-repo            /workspace           res-ref rw    │
+    │ /home/user/.ssh           /home/dev/.ssh       host    ro    │
+    ╰───────────────────────────────────────────────────────────────────╯
+
+    ✓ OK Container resource registered
+    
+ +=== "Plain" + + ``` + $ agents resource add git-checkout local/api-repo --path /home/user/projects/api + [OK] Resource registered: local/api-repo (127 children) + + $ agents resource add container-instance local/api-container \ + --mount local/api-repo:/workspace \ + --mount /home/user/.ssh:/home/dev/.ssh:ro + + Resource + Name: local/api-container + ID: 01J0C3D4E5F6G7H8J9K0L1M2N3 + Type: container-instance + State: created (not started) + + Mounts + Source Container Path Kind Mode + ------------------------ ------------------ ------ ---- + local/api-repo /workspace res-ref rw + /home/user/.ssh /home/dev/.ssh host ro + + [OK] Container resource registered + ``` + +**Step 2 — Create project, link resources, set execution environment:** + +=== "Rich" + +

+    $ agents project create local/api-project
+    ✓ OK Project created
+
+    $ agents project link-resource local/api-project local/api-repo
+    ✓ OK Resource linked
+
+    $ agents project link-resource local/api-project local/api-container
+    ✓ OK Resource linked
+
+    $ agents project context set \
+      --execution-environment local/api-container \
+      --execution-env-priority override \
+      local/api-project
+
+    ╭─ Context Policy Updated ──────────────────────────╮
+    │ Execution Environment: local/api-container         │
+    │ Priority: override (precedence level 2)            │
+    │ Note: All tool invocations will route to this       │
+    │       container, bypassing devcontainer detection  │
+    ╰───────────────────────────────────────────────────╯
+
+    ✓ OK Context policy updated
+    
+ +=== "Plain" + + ``` + $ agents project create local/api-project + [OK] Project created + + $ agents project link-resource local/api-project local/api-repo + [OK] Resource linked + + $ agents project link-resource local/api-project local/api-container + [OK] Resource linked + + $ agents project context set \ + --execution-environment local/api-container \ + --execution-env-priority override \ + local/api-project + + Context Policy Updated + Execution Environment: local/api-container + Priority: override (precedence level 2) + Note: All tool invocations will route to this + container, bypassing devcontainer detection + + [OK] Context policy updated + ``` + +**Step 3 — Execute a plan (container started, tools routed):** + +=== "Rich" + +

+    $ agents plan use local/fix-auth-bug local/api-project --automation-profile trusted
+
+    ✓ OK Plan 01J0D4E5F6G7H8J9K0L1M2N3O4 created — strategize phase starting
+
+    $ agents plan execute 01J0D4E5F6G7H8J9K0L1M2N3O4
+
+    ⏳ Starting container local/api-container …
+    ✓ Container ready in 3s
+
+    ╭─ Execution Environment ───────────────────────────╮
+    │ Resolved via: project override                     │
+    │ Resource: local/api-container                      │
+    │ Workspace: /workspace                              │
+    │ Precedence: level 2 of 6                           │
+    ╰───────────────────────────────────────────────────╯
+
+    ▸ Decision 1/2: Fix token validation in src/auth/jwt.ts
+      → edit_file() in container /workspace/src/auth/jwt.ts
+       Checkpoint cp-001
+
+    ▸ Decision 2/2: Run auth test suite
+      → run_command() in container npm test -- --grep auth
+       12/12 tests passed
+       Checkpoint cp-002
+
+    ✓ OK Execution complete — ready for apply
+    
+ +=== "Plain" + + ``` + $ agents plan use local/fix-auth-bug local/api-project --automation-profile trusted + [OK] Plan 01J0D4E5F6G7H8J9K0L1M2N3O4 created — strategize phase starting + + $ agents plan execute 01J0D4E5F6G7H8J9K0L1M2N3O4 + + [WAIT] Starting container local/api-container … + [OK] Container ready in 3s + + Execution Environment + Resolved via: project override + Resource: local/api-container + Workspace: /workspace + Precedence: level 2 of 6 + + Decision 1/2: Fix token validation in src/auth/jwt.ts + -> edit_file() in container /workspace/src/auth/jwt.ts + [OK] Checkpoint cp-001 + + Decision 2/2: Run auth test suite + -> run_command() in container npm test -- --grep auth + [OK] 12/12 tests passed + [OK] Checkpoint cp-002 + + [OK] Execution complete — ready for apply + ``` + +### Example 18: Container with Remote Repo Clone + +!!! adr "Related ADRs" + [ADR-043: Devcontainer Integration](adr/ADR-043-devcontainer-integration.md) · [ADR-039: Container Resource Types](adr/ADR-039-container-resource-types.md) + +**Scenario:** A CI/CD pipeline creates a container-instance that clones a remote repository on first start. There is no local checkout — the code lives entirely inside the container. This pattern is useful for cloud-based or ephemeral build environments where the operator has no local filesystem access to the repository. + +**What this example demonstrates:** + +- `--clone-into` flag on `agents resource add container-instance` +- Remote repository cloned lazily on container first start +- Plan-level execution environment with `fallback` priority (precedence level 4) +- No local git-checkout or devcontainer involved + +**Step 1 — Create container resource with clone-into:** + +=== "Rich" + +

+    $ agents resource add container-instance cloud/build-env \
+      --clone-into https://github.com/acme/billing-api.git:/workspace
+
+    ╭─ Resource ──────────────────────────────────────────╮
+    │ Name: cloud/build-env                               │
+    │ ID: 01J0E5F6G7H8J9K0L1M2N3O4P5                      │
+    │ Type: container-instance                            │
+    │ State: created (not started)                        │
+    │ Clone: https://github.com/acme/billing-api.git      │
+    │ Clone Target: /workspace                             │
+    ╰─────────────────────────────────────────────────────╯
+
+    ✓ OK Container resource registered (repo will be cloned on first start)
+    
+ +=== "Plain" + + ``` + $ agents resource add container-instance cloud/build-env \ + --clone-into https://github.com/acme/billing-api.git:/workspace + + Resource + Name: cloud/build-env + ID: 01J0E5F6G7H8J9K0L1M2N3O4P5 + Type: container-instance + State: created (not started) + Clone: https://github.com/acme/billing-api.git + Clone Target: /workspace + + [OK] Container resource registered (repo will be cloned on first start) + ``` + +**Step 2 — Create project and link the container:** + +=== "Rich" + +

+    $ agents project create cloud/billing-api
+    ✓ OK Project created
+
+    $ agents project link-resource cloud/billing-api cloud/build-env
+    ✓ OK Resource linked
+    
+ +=== "Plain" + + ``` + $ agents project create cloud/billing-api + [OK] Project created + + $ agents project link-resource cloud/billing-api cloud/build-env + [OK] Resource linked + ``` + +**Step 3 — Execute plan with plan-level execution environment:** + +=== "Rich" + +

+    $ agents plan use local/add-pagination cloud/billing-api \
+      --execution-environment cloud/build-env \
+      --execution-env-priority fallback \
+      --automation-profile trusted
+
+    ✓ OK Plan 01J0F6G7H8J9K0L1M2N3O4P5Q6 created
+
+    $ agents plan execute 01J0F6G7H8J9K0L1M2N3O4P5Q6
+
+    ⏳ Starting container cloud/build-env …
+    ⏳ Cloning https://github.com/acme/billing-api.git into /workspace …
+    ✓ Clone complete (1.2 GB, 47s)
+    ✓ Container ready in 52s
+
+    ╭─ Execution Environment ───────────────────────────╮
+    │ Resolved via: plan fallback                        │
+    │ Resource: cloud/build-env                          │
+    │ Workspace: /workspace                              │
+    │ Precedence: level 4 of 6                           │
+    │ Note: No devcontainer detected; using plan fallback │
+    ╰───────────────────────────────────────────────────╯
+
+    ▸ Decision 1/4: Add cursor-based pagination to /workspace/src/api/invoices.ts
+      → edit_file() in container
+       Checkpoint cp-001
+
+    ▸ Decision 2/4: Add pagination types to /workspace/src/types/pagination.ts
+      → write_file() in container
+       Checkpoint cp-002
+
+    ▸ Decision 3/4: Update API tests
+      → edit_file() in container
+       Checkpoint cp-003
+
+    ▸ Decision 4/4: Run test suite
+      → run_command() in container npm test
+       89/89 tests passed
+       Checkpoint cp-004
+
+    ✓ OK Execution complete — ready for apply
+    
+ +=== "Plain" + + ``` + $ agents plan use local/add-pagination cloud/billing-api \ + --execution-environment cloud/build-env \ + --execution-env-priority fallback \ + --automation-profile trusted + + [OK] Plan 01J0F6G7H8J9K0L1M2N3O4P5Q6 created + + $ agents plan execute 01J0F6G7H8J9K0L1M2N3O4P5Q6 + + [WAIT] Starting container cloud/build-env … + [WAIT] Cloning https://github.com/acme/billing-api.git into /workspace … + [OK] Clone complete (1.2 GB, 47s) + [OK] Container ready in 52s + + Execution Environment + Resolved via: plan fallback + Resource: cloud/build-env + Workspace: /workspace + Precedence: level 4 of 6 + Note: No devcontainer detected; using plan fallback + + Decision 1/4: Add cursor-based pagination to /workspace/src/api/invoices.ts + -> edit_file() in container + [OK] Checkpoint cp-001 + + Decision 2/4: Add pagination types to /workspace/src/types/pagination.ts + -> write_file() in container + [OK] Checkpoint cp-002 + + Decision 3/4: Update API tests + -> edit_file() in container + [OK] Checkpoint cp-003 + + Decision 4/4: Run test suite + -> run_command() in container npm test + [OK] 89/89 tests passed + [OK] Checkpoint cp-004 + + [OK] Execution complete — ready for apply + ``` + +**Step 4 — Apply (changes committed inside container, pushed to remote):** + +=== "Rich" + +

+    $ agents plan apply --yes 01J0F6G7H8J9K0L1M2N3O4P5Q6
+
+    ╭─ Apply ──────────────────────────────────────────╮
+    │ Files modified: 3                                │
+    │   ~ src/api/invoices.ts (modified)              │
+    │   + src/types/pagination.ts (new)               │
+    │   ~ tests/api/invoices.test.ts (modified)       │
+    │ Target: container /workspace                     │
+    │ Apply mode: commit + push to origin/main          │
+    ╰──────────────────────────────────────────────────╯
+
+    ✓ OK Changes applied (committed as abc1234, pushed to origin/main)
+    
+ +=== "Plain" + + ``` + $ agents plan apply --yes 01J0F6G7H8J9K0L1M2N3O4P5Q6 + + Apply + Files modified: 3 + ~ src/api/invoices.ts (modified) + + src/types/pagination.ts (new) + ~ tests/api/invoices.test.ts (modified) + Target: container /workspace + Apply mode: commit + push to origin/main + + [OK] Changes applied (committed as abc1234, pushed to origin/main) + ``` + ## Architecture This section provides the complete architectural blueprint for CleverAgents. It defines the system's structural composition, technology choices, layer boundaries, data flow, and operational characteristics. An implementor should be able to build the entire system from this section combined with the behavioral specification above. @@ -40584,6 +41616,7 @@ This section enumerates every technology choice in the CleverAgents stack, organ | **Helm** | (chart) | Kubernetes deployment | Helm chart in `k8s/` directory for server mode deployment. Linted and template-tested in CI. | | **Nox** | >= 2025.4.22 | Task automation | Session-based task runner for lint, test, build, docs, benchmarks, and coverage. Uses `uv` as the virtual environment backend for fast session creation. | | **Forgejo CI** | (workflow) | Continuous integration | Self-hosted Forgejo instance with GitHub Actions-compatible workflow syntax. Pipeline: lint -> typecheck -> behave (matrix 3.11/3.12/3.13) -> build -> docker -> helm. | +| **devcontainer CLI** | (optional) | Devcontainer management | Used for building and managing `devcontainer-instance` resources from `.devcontainer/devcontainer.json` configurations. Falls back to direct Docker/Podman CLI when not installed, with reduced feature support (e.g., devcontainer features may not be available). See [ADR-043](adr/ADR-043-devcontainer-integration.md). | #### Monitoring and Observability -- 2.52.0