# Resource DAG Resources in CleverAgents form a **directed acyclic graph** (DAG) where parent resources contain or reference child resources. This document describes the linking rules, cycle detection, type compatibility enforcement, and auto-discovery behaviour. ## DAG Rules | Rule | Description | |------|-------------| | **No self-loops** | A resource cannot be its own child. | | **No cycles** | If resource A is an ancestor of B, then B cannot become a parent of A. | | **Type compatibility** | The child's resource type must appear in the parent type's `child_types` list. | | **Unique links** | A given (parent, child) pair can only be linked once. | | **Both must exist** | Both the parent and the child resource must be registered before linking. | ## API ### `link_child(parent_id, child_id)` Links a child resource to a parent in the DAG. 1. Validates that both resources exist in the registry. 2. Checks **type compatibility** — the child resource's type must be listed in the parent resource type's `child_types` field. 3. Performs **cycle detection** — walks the ancestor chain of the parent to ensure the child is not already an ancestor. 4. Persists the link in the `resource_links` table. **Errors:** - `ResourceNotFoundRepoError` — parent or child does not exist. - `TypeIncompatibleError` — child type not in parent's `child_types`. - `CycleDetectedError` — linking would create a cycle. - `DuplicateResourceLinkError` — link already exists. ### `unlink_child(parent_id, child_id)` Removes a parent-child link from the DAG. 1. Validates that both resources exist. 2. Validates the link exists. 3. Deletes the link from `resource_links`. **Errors:** - `ResourceNotFoundRepoError` — parent or child does not exist. - `LinkNotFoundError` — the link does not exist. ### `get_children(resource_id)` Returns all direct children of a resource (via `resource_links`). ### `get_parents(resource_id)` Returns all direct parents of a resource (via `resource_links`). ## Cycle Detection Cycle detection uses a **breadth-first search** upward through the `resource_links` table starting from the proposed parent. If the proposed child is found among the ancestors, the link is rejected with a `CycleDetectedError` that includes the cycle path. ### Example ``` A -> B -> C ``` Attempting to link `C -> A` would be rejected because `A` is an ancestor of `C`. The error message includes the path: `A -> B -> C -> A`. ## Type Compatibility Each resource type defines a `child_types` list of allowed child type names. When linking, the system verifies: ``` child.resource_type_name in parent_type.child_types ``` If the parent type's `child_types` list is empty, **any** child type is allowed (no restriction). ### Example ```yaml # git-checkout type child_types: ["fs-directory", "git"] ``` Only resources of type `fs-directory` or `git` can be linked as children of a `git-checkout` resource. ## Auto-Discovery ### `auto_discover_children(resource_id)` Materializes child resources based on the parent's type auto-discovery configuration. 1. Looks up the resource and its type. 2. Reads the `auto_discovery` configuration from the type. 3. For each discovery rule where `enabled` is `true`: - Checks the child type exists in the database. - Checks type compatibility with the parent. - Creates a new child resource with `auto_discovered = true`. - Links the child to the parent via `resource_links`. 4. Returns the list of newly created child resources. ### Auto-Discovery Configuration Auto-discovery is configured per resource type in YAML: ```yaml auto_discovery: enabled: true rules: - type: fs-directory pattern: "*/" - type: fs-file pattern: "*" ``` Each rule specifies: - `type` — the child resource type name to create. - `pattern` — a glob pattern (used by handlers for actual file discovery; the repository layer creates placeholder entries). ### When Does Auto-Discovery Run? Auto-discovery is triggered by calling `auto_discover_children(resource_id)`. This is typically invoked: - When a resource is first registered. - When a resource's contents change (e.g., new files appear). - On demand via CLI commands. ## Database Schema ### `resource_links` Table | Column | Type | Description | |--------|------|-------------| | `parent_id` | `String(26)` | FK to `resources.resource_id` | | `child_id` | `String(26)` | FK to `resources.resource_id` | | `created_at` | `String(30)` | ISO-8601 timestamp | Primary key: `(parent_id, child_id)` Constraints: - `parent_id != child_id` (no self-loops) - Foreign keys cascade on delete ### `resource_edges` Table (Extended) | Column | Type | Description | |--------|------|-------------| | `parent_id` | `String(26)` | FK to `resources.resource_id` | | `child_id` | `String(26)` | FK to `resources.resource_id` | | `link_type` | `String(20)` | `contains`, `references`, or physical-to-virtual | | `auto_discovered` | `Boolean` | Whether the link was auto-discovered | | `coherence` | `String(20)` | `transparent`, `cached`, `independent`, or `NULL` (only set on physical-to-virtual edges) | | `created_at` | `String(30)` | ISO-8601 timestamp | ## Operational Semantics Beyond structural rules, the DAG supports runtime operations. For full details, see [ADR-036](../adr/ADR-036-resource-dag-operational-semantics.md), [ADR-037](../adr/ADR-037-tool-reachability-and-access-projection.md), and [ADR-038](../adr/ADR-038-cross-mechanism-sandbox-coordination.md). ### Sandbox Boundary Algebra - **`sandbox_boundary(r)`**: Nearest sandboxable ancestor of `r` along `contains` edges. - **Sandbox domain**: All resources sharing the same sandbox boundary share one sandbox instance. - Resources below a boundary (e.g., files within a git-checkout) cannot be sandboxed independently. ### Tool Reachability - **Forward**: Tool bound to `R` can reach `R` and all `contains` descendants. - **Inverse**: Resource `r` is reachable by tools bound to any of `r`'s containment ancestors. - **Cross-equivalence**: Tools reaching one physical manifestation of a virtual resource also logically reach all equivalent manifestations. ### Access Projection Each resource type handler implements `project_access()` to compute how a tool bound to an ancestor reaches a descendant resource (access path, protocol, sandbox-crossing awareness, read richness score). ### Read/Write Routing Virtual resources with multiple physical manifestations route: - **Reads** through the richest available source (LSP > filesystem > raw). - **Writes** through the canonical write target (strongest sandbox domain). ### Cross-Mechanism Coordination - **Coherence**: `transparent` (shared storage), `cached` (needs refresh), `independent` (needs sync). - **Write-then-sync**: Changes propagate at commit time, not during execution. - **Conflict detection**: Multiple dirty manifestations of the same virtual resource at commit time trigger resolution (canonical-wins, merge, fail, or last-writer-wins).