diff --git a/docs/specification.md b/docs/specification.md
index b96c9906..038e6a03 100644
--- a/docs/specification.md
+++ b/docs/specification.md
@@ -4143,17 +4143,18 @@ This is important because:
3. **Unified correction mechanism**: Since the prompt is just another decision, correcting it uses the same `agents plan correct` command as any other decision.
```kroki-mermaid
-flowchart TD
- A["prompt_definition\nIncrease test coverage to 85%"] --> B["strategy_choice\nPrioritize auth and payment modules"]
- A --> C["subplan_spawn\nWrite tests for auth module"]
- A --> D["subplan_spawn\nWrite tests for payment module"]
- C --> C1["Subplan: auth-tests"]
- C1 --> C1a["prompt_definition\nWrite unit tests for auth module using mocks"]
- C1 --> C1b["implementation_choice\nTest login flow first"]
- C1 --> C1c["..."]
- D --> D1["Subplan: payment-tests"]
- D1 --> D1a["prompt_definition\nWrite unit tests for payment module"]
- D1 --> D1b["..."]
+mindmap
+ root(("prompt_definition
Increase test coverage to 85%"))
+ ("strategy_choice
Prioritize auth and
payment modules")
+ ("subplan_spawn
Write tests for auth module")
+ ("Subplan: auth-tests")
+ ("prompt_definition
Write unit tests for
auth module using mocks")
+ ("implementation_choice
Test login flow first")
+ ("...")
+ ("subplan_spawn
Write tests for payment module")
+ ("Subplan: payment-tests")
+ ("prompt_definition
Write unit tests for
payment module")
+ ("...")
```
##### Correcting Decisions (Including Prompts)
@@ -4886,12 +4887,26 @@ This architecture provides:
##### How It Works
```kroki-mermaid
-flowchart TD
- A["LLM Response (with tool calls)"] --> B
- B["Tool Router\n- Routes each tool call to handler\n- Validates parameters\n- Enforces capability restrictions"] --> C
- C["Sandbox Execution\n- Tool operates on sandboxed state\n- Each invocation recorded\n- Checkpoint created if needed"] --> D
- D["ChangeSet Accumulation\n- Each resource-modifying call becomes a Change record\n- ChangeSet = history of changes"] --> E
- E["Validation & Review\n- Run validators on sandbox state\n- Generate diff from ChangeSet\n- Present for review before Apply"]
+sequenceDiagram
+ participant LLM as LLM Agent
+ participant Router as Tool Router
+ participant Sandbox as Sandbox
+ participant CS as ChangeSet
+ participant Val as Validator
+
+ LLM->>Router: Tool call (with parameters)
+ Router->>Router: Validate parameters
+ Router->>Router: Enforce capability restrictions
+ Router->>Sandbox: Execute tool in sandbox
+ Sandbox->>Sandbox: Operate on sandboxed state
+ Sandbox->>Sandbox: Record invocation
+ Sandbox->>Sandbox: Create checkpoint (if needed)
+ Sandbox->>CS: Emit Change record
+ CS->>CS: Accumulate into ChangeSet
+ CS->>Val: Submit for validation
+ Val->>Val: Run validators on sandbox state
+ Val->>Val: Generate diff from ChangeSet
+ Val-->>LLM: Present for review before Apply
```
##### Built-in Resource Tools
@@ -5578,14 +5593,29 @@ Tools serve two distinct roles in CleverAgents:
2. **As tool nodes in an Actor graph**: An actor's graph definition can include `type: tool` nodes that directly invoke a specific tool. This is used for deterministic, non-LLM steps in a workflow — e.g., spawning a subplan, running validation, or executing a migration. The tool node either references a named registered tool or defines an anonymous inline tool.
```kroki-mermaid
-flowchart LR
- subgraph Tool["Tool: Dual Role"]
- subgraph R1["Role 1: In a Skill"]
- S1["Skill: local/devops\n tools:\n - local/run-migrations\n - local/validate-schema\n (tool-calling by LLM)"]
- end
- subgraph R2["Role 2: In an Actor Graph"]
- S2["Actor Graph:\n nodes:\n - name: run_db\n type: tool\n tool: local/run-migrations\n (deterministic invoke)"]
- end
+block-beta
+ columns 3
+ space:3
+ block:header:3
+ A["Tool: Dual Role"]
+ end
+ space:3
+ block:role1:1
+ B["Role 1: In a Skill"]
+ C["Skill: local/devops"]
+ D["tools:"]
+ E[" - local/run-migrations"]
+ F[" - local/validate-schema"]
+ G["(tool-calling by LLM)"]
+ end
+ space:1
+ block:role2:1
+ H["Role 2: In an Actor Graph"]
+ I["Actor Graph node:"]
+ J[" name: run_db"]
+ K[" type: tool"]
+ L[" tool: local/run-migrations"]
+ M["(deterministic invoke)"]
end
```
@@ -5960,26 +5990,55 @@ The `from_param` field links a resource slot to an input parameter. At invocatio
##### Binding Resolution Flow
```kroki-mermaid
-flowchart TD
- A["Tool activation\n(actor references skill or tool node)"] --> B["For each resource slot in the tool"]
- B --> C{"Binding type?"}
- C -->|"Static\n(has bind field)"| D["Resolve from Resource Registry by name\n→ Validate type compatibility"]
- C -->|"Contextual\n(no bind, no from_param)"| E["Search plan's project linked resources"]
- E --> E1["Filter by resource type"]
- E1 --> E2{"Match count?"}
- E2 -->|"One"| E3["Auto-bind"]
- E2 -->|"Multiple"| E4["Try alias/name match, else error"]
- E2 -->|"None"| E5["Validation error"]
- C -->|"Parameter\n(has from_param)"| F["Defer to invocation time"]
- D --> G["Store resolved bindings\nin ToolActivationContext"]
- E3 --> G
- F --> G
+stateDiagram-v2
+ [*] --> ToolActivation: Actor references skill or tool node
- G --> H["Tool invocation"]
- H --> I["For parameter-bound slots:\nResolve resource name from params\n→ Validate type + access compatibility"]
- H --> J["For all bound resources:\n- Ensure sandbox exists\n- Validate access mode\n- Inject into ToolExecutionContext"]
- I --> K["Execute tool with\nbound resources available"]
- J --> K
+ state "For Each Resource Slot" as ForEach {
+ state binding_check <>
+ [*] --> binding_check: Check binding type
+
+ binding_check --> StaticBinding: has bind field
+ binding_check --> ContextualBinding: no bind, no from_param
+ binding_check --> ParameterBinding: has from_param
+
+ state "Static Binding" as StaticBinding {
+ [*] --> ResolveByName: Resolve from Resource Registry
+ ResolveByName --> ValidateType: Validate type compatibility
+ }
+
+ state "Contextual Binding" as ContextualBinding {
+ [*] --> SearchProject: Search plan's project resources
+ SearchProject --> FilterByType: Filter by resource type
+ state match_check <>
+ FilterByType --> match_check
+ match_check --> AutoBind: One match
+ match_check --> TryAlias: Multiple matches
+ match_check --> ValidationError: No matches
+ TryAlias --> AliasMatch: Try alias/name match
+ AliasMatch --> AutoBind: Match found
+ AliasMatch --> ValidationError: No match
+ }
+
+ state "Parameter Binding" as ParameterBinding {
+ [*] --> DeferBinding: Defer to invocation time
+ }
+
+ StaticBinding --> StoreBindings
+ AutoBind --> StoreBindings
+ ParameterBinding --> StoreBindings
+ state "Store in ToolActivationContext" as StoreBindings
+ }
+
+ state "Tool Invocation" as Invocation {
+ [*] --> ResolveParams: Resolve parameter-bound slots\nfrom invocation params
+ [*] --> ValidateAccess: Ensure sandbox exists\nValidate access mode
+ ResolveParams --> Execute
+ ValidateAccess --> Execute
+ state "Execute tool with bound resources" as Execute
+ }
+
+ ForEach --> Invocation
+ Invocation --> [*]
```
##### Built-in Tool Resource Bindings
@@ -6008,30 +6067,51 @@ The binding system enables powerful resource discovery queries:
CleverAgents maintains a **Tool Registry** — a persistent catalog of all independently registered tools:
-```kroki-mermaid
-classDiagram
- class ToolRegistry {
- +Tool Index
- +add(config_path) ToolRecord
- +upgrade(name, config_path) ToolRecord
- +remove(name) void
- +lookup(name) ToolRecord
- +list(filters) ToolRecord[]
- }
- class ToolRecord {
- +name : str
- +description : str
- +tags : list
- +source : str
- +config_path : str
- +input_schema : JSONSchema
- +output_schema : JSONSchema
- +capability_metadata : dict
- +resource_slots : list~ResourceSlot~
- +code : str
- }
- ToolRegistry --> ToolRecord : indexes
- note for ToolRegistry "Populated by:\n- agents tool add CLI command\n- Dynamic refresh on MCP notifications\n\nConsumed by:\n- Skill registration\n- Actor graph construction\n- Resource binding resolution\n- Plan validation\n- Permission enforcement"
+```kroki-plantuml
+@startuml
+skinparam classAttributeIconSize 0
+skinparam classFontSize 13
+skinparam noteFontSize 11
+skinparam defaultFontSize 12
+
+class ToolRegistry {
+ - toolIndex : Map
+ --
+ + add(config_path) : ToolRecord
+ + upgrade(name, config_path) : ToolRecord
+ + remove(name) : void
+ + lookup(name) : ToolRecord
+ + list(filters) : ToolRecord[]
+}
+
+class ToolRecord {
+ + name : String
+ + description : String
+ + tags : List
+ + source : String {mcp|agent_skill|builtin|custom}
+ + config_path : String
+ + input_schema : JSONSchema
+ + output_schema : JSONSchema
+ + capability_metadata : CapabilityMetadata
+ + resource_slots : List
+ + code : String
+}
+
+ToolRegistry "1" *-- "0..*" ToolRecord : indexes >
+
+note right of ToolRegistry
+ **Populated by:**
+ - agents tool add CLI command
+ - Dynamic refresh on MCP notifications
+
+ **Consumed by:**
+ - Skill registration
+ - Actor graph construction
+ - Resource binding resolution
+ - Plan validation
+ - Permission enforcement
+end note
+@enduml
```
The Tool Registry works alongside the Skill Registry (described in the Skills section). Skills reference tools by name from the Tool Registry; the Skill Registry's flattened tool sets are composed from Tool Registry entries plus any anonymous inline tools.
@@ -6040,66 +6120,82 @@ The Tool Registry works alongside the Skill Registry (described in the Skills se
Each individual tool — whether independently registered or defined as an anonymous inline tool — conforms to a uniform interface regardless of its source:
-```kroki-mermaid
-classDiagram
- class Tool {
- +identity
- +schema
- +capability_metadata
- +resource_bindings
- +lifecycle
- +execution_context
- }
- class Identity {
- +name : str
- +qualified_name : str
- +source : enum
- }
- class Schema {
- +input_schema : JSONSchema
- +output_schema : JSONSchema
- }
- class CapabilityMetadata {
- +read_only : bool
- +writes : bool
- +write_scope : str
- +idempotent : bool
- +checkpointable : bool
- +side_effects : list
- +required_permissions : list
- +cost_profile : str
- +human_approval_required : bool
- }
- class ResourceBindings {
- +slots : dict~str, ResourceSlot~
- }
- class ResourceSlot {
- +type : str
- +access : str
- +required : bool
- +bind : str
- +from_param : str
- +description : str
- }
- class Lifecycle {
- +discover() ToolDescriptor
- +activate() void
- +execute(params, ctx) Result
- +deactivate() void
- }
- class ExecutionContext {
- +sandbox : Sandbox
- +plan : Plan
- +changes : list~Change~
- +resources : dict~str, BoundResource~
- }
- Tool --> Identity
- Tool --> Schema
- Tool --> CapabilityMetadata
- Tool --> ResourceBindings
- ResourceBindings --> ResourceSlot
- Tool --> Lifecycle
- Tool --> ExecutionContext
+```kroki-plantuml
+@startuml
+skinparam classAttributeIconSize 0
+skinparam classFontSize 13
+skinparam defaultFontSize 12
+skinparam linetype ortho
+
+class Tool {
+}
+
+class Identity {
+ + name : String
+ + qualified_name : String
+ + source : ToolSource
+}
+
+class Schema {
+ + input_schema : JSONSchema
+ + output_schema : JSONSchema
+}
+
+class CapabilityMetadata {
+ + read_only : Boolean
+ + writes : Boolean
+ + write_scope : String
+ + idempotent : Boolean
+ + checkpointable : Boolean
+ + side_effects : List
+ + required_permissions : List
+ + cost_profile : String
+ + human_approval_required : Boolean
+}
+
+class ResourceBindings {
+ + slots : Map
+}
+
+class ResourceSlot {
+ + type : String
+ + access : String
+ + required : Boolean
+ + bind : String
+ + from_param : String
+ + description : String
+}
+
+interface Lifecycle <> {
+ + discover() : ToolDescriptor
+ + activate() : void
+ + execute(params, ctx) : Result
+ + deactivate() : void
+}
+
+class ExecutionContext {
+ + sandbox : Sandbox
+ + plan : Plan
+ + changes : List
+ + resources : Map
+}
+
+enum ToolSource {
+ mcp
+ agent_skill
+ builtin
+ custom
+}
+
+Tool *-- Identity
+Tool *-- Schema
+Tool *-- CapabilityMetadata
+Tool *-- ResourceBindings
+Tool *-- Lifecycle
+Tool o-- ExecutionContext : uses at runtime >
+ResourceBindings *-- "0..*" ResourceSlot
+Identity --> ToolSource
+@enduml
```
Every tool implements the same four lifecycle methods. The **tool adapter layer** is responsible for translating source-specific behavior into these methods.
@@ -6108,32 +6204,56 @@ Every tool implements the same four lifecycle methods. The **tool adapter layer*
Each tool source has a corresponding **adapter** that translates source-specific protocols into the uniform tool interface:
-```kroki-mermaid
-flowchart TD
- subgraph AdapterLayer["Tool Adapter Layer"]
- subgraph MCP["MCPToolAdapter"]
- M1["discover(): tools/list RPC → descriptors"]
- M2["activate(): spawn server, init JSON-RPC"]
- M3["execute(): tools/call RPC → result"]
- M4["deactivate(): shutdown server"]
- end
- subgraph AS["AgentSkillAdapter"]
- A1["discover(): parse SKILL.md frontmatter → descriptor"]
- A2["activate(): load full SKILL.md body into agent context"]
- A3["execute(): agent follows instructions, runs scripts"]
- A4["deactivate(): remove from ctx"]
- end
- subgraph BI["BuiltinAdapter"]
- B1["discover(): return hardcoded descriptors"]
- B2["activate(): no-op"]
- B3["execute(): call native Python impl"]
- B4["deactivate(): no-op"]
- end
- end
- MCP --> UTI
- AS --> UTI
- BI --> UTI
- UTI["Uniform Tool Interface\ndiscover() → meta\nactivate() → void\nexecute(p, ctx) → R\ndeactivate() → void"]
+```kroki-plantuml
+@startuml
+skinparam classAttributeIconSize 0
+skinparam classFontSize 13
+skinparam defaultFontSize 12
+
+interface "ToolInterface" as UTI <> {
+ + discover() : ToolDescriptor
+ + activate() : void
+ + execute(params, ctx) : Result
+ + deactivate() : void
+}
+
+class MCPToolAdapter {
+ + discover() : ToolDescriptor
+ .. tools/list RPC → descriptors ..
+ + activate() : void
+ .. spawn server, init JSON-RPC ..
+ + execute(params, ctx) : Result
+ .. tools/call RPC → result ..
+ + deactivate() : void
+ .. shutdown server ..
+}
+
+class AgentSkillAdapter {
+ + discover() : ToolDescriptor
+ .. parse SKILL.md frontmatter ..
+ + activate() : void
+ .. load SKILL.md body into agent context ..
+ + execute(params, ctx) : Result
+ .. agent follows instructions, runs scripts ..
+ + deactivate() : void
+ .. remove from context ..
+}
+
+class BuiltinAdapter {
+ + discover() : ToolDescriptor
+ .. return hardcoded descriptors ..
+ + activate() : void
+ .. no-op ..
+ + execute(params, ctx) : Result
+ .. call native Python impl ..
+ + deactivate() : void
+ .. no-op ..
+}
+
+MCPToolAdapter .up.|> UTI
+AgentSkillAdapter .up.|> UTI
+BuiltinAdapter .up.|> UTI
+@enduml
```
##### MCPToolAdapter
@@ -6390,24 +6510,44 @@ This approach means:
CleverAgents manages MCP server processes as part of the tool/skill/actor lifecycle:
```kroki-mermaid
-flowchart TD
- A["Tool or skill registration\n(agents tool add / agents skill add)"] --> A1["For each MCP server in config"]
- A1 --> A2["Validate server command/endpoint"]
- A1 --> A3["Store server config in record"]
+sequenceDiagram
+ participant CLI as CLI / User
+ participant Reg as Tool/Skill Registry
+ participant Actor as Actor Runtime
+ participant Adapter as MCPToolAdapter
+ participant MCP as MCP Server
- B["Actor activation\n(references skills or tool nodes)"] --> B1["For each MCP server in referenced tools/skills"]
- B1 --> B2["Spawn server process (stdio) or connect (HTTP)"]
- B1 --> B3["MCP initialize handshake"]
- B1 --> B4["tools/list → register tools"]
- B1 --> B5["Subscribe to notifications/tools/list_changed"]
+ rect rgb(240, 248, 255)
+ note over CLI,Reg: Phase 1: Registration
+ CLI->>Reg: agents tool add / agents skill add
+ Reg->>Reg: Validate server command/endpoint
+ Reg->>Reg: Store server config in record
+ end
- C["Actor graph execution"] --> C1["LLM agent generates tool call"]
- C1 --> C2["Tool Router → MCPToolAdapter.execute()"]
- C2 --> C3["tools/call JSON-RPC → MCP server"]
- C3 --> C4["Result → Change tracking → Return"]
+ rect rgb(240, 255, 240)
+ note over Actor,MCP: Phase 2: Actor Activation
+ Actor->>Adapter: Activate (for each MCP server)
+ Adapter->>MCP: Spawn process (stdio) or connect (HTTP)
+ Adapter->>MCP: MCP initialize handshake
+ MCP-->>Adapter: Capabilities
+ Adapter->>MCP: tools/list
+ MCP-->>Adapter: Tool descriptors
+ Adapter->>MCP: Subscribe notifications/tools/list_changed
+ end
- D["Actor deactivation"] --> D1["For each MCP server"]
- D1 --> D2["Clean shutdown of server process / connection"]
+ rect rgb(255, 255, 240)
+ note over Actor,MCP: Phase 3: Execution
+ Actor->>Adapter: LLM generates tool call
+ Adapter->>MCP: tools/call (JSON-RPC)
+ MCP-->>Adapter: Result
+ Adapter->>Actor: Result + Change tracking
+ end
+
+ rect rgb(255, 240, 240)
+ note over Actor,MCP: Phase 4: Deactivation
+ Actor->>Adapter: Deactivate
+ Adapter->>MCP: Clean shutdown
+ end
```
##### Sandbox Path Rewriting for MCP Tools
@@ -6495,25 +6635,40 @@ A skill is **not** a single tool. It is a **container** that references one or m
#### The Skill / Tool Distinction
-```kroki-mermaid
-flowchart TD
- subgraph Skill["Skill: local/devops-toolkit (a named, reusable collection)"]
- subgraph Tools["Tools (from various sources)"]
- T1["read_file() - builtin: file_operations"]
- T2["write_file() - builtin: file_operations"]
- T3["edit_file() - builtin: file_operations"]
- T4["git_status() - builtin: git_operations"]
- T5["git_diff() - builtin: git_operations"]
- T6["create_issue() - mcp: github-server"]
- T7["create_pr() - mcp: github-server"]
- T8["list_repos() - mcp: github-server"]
- T9["run_migrations() - custom"]
- end
- subgraph Includes["Included Skills (hierarchical)"]
- I1["local/pdf-processing → adds pdf tools"]
- I2["local/data-analysis → adds analysis tools"]
- end
- end
+```kroki-plantuml
+@startuml
+skinparam packageStyle rectangle
+skinparam defaultFontSize 12
+skinparam componentFontSize 12
+
+package "Skill: local/devops-toolkit" as Skill {
+ package "builtin: file_operations" as FileOps {
+ component [read_file()] as T1
+ component [write_file()] as T2
+ component [edit_file()] as T3
+ }
+
+ package "builtin: git_operations" as GitOps {
+ component [git_status()] as T4
+ component [git_diff()] as T5
+ }
+
+ package "mcp: github-server" as GH {
+ component [create_issue()] as T6
+ component [create_pr()] as T7
+ component [list_repos()] as T8
+ }
+
+ package "custom" as Custom {
+ component [run_migrations()] as T9
+ }
+
+ package "Included Skills" as Includes {
+ component [local/pdf-processing\n(adds pdf tools)] as I1
+ component [local/data-analysis\n(adds analysis tools)] as I2
+ }
+}
+@enduml
```
**Tools** are independently registered, atomic units of execution (see the **Tools** section above for full details). Each tool has:
@@ -6797,33 +6952,55 @@ To enable plan validation, permission enforcement, and discovery at scale, Cleve
2. **Skill Registry** — a persistent catalog of all registered skills and their flattened tool sets. The Skill Registry composes its tool sets by resolving named tool references from the Tool Registry, incorporating anonymous inline tools, and merging tools from included child skills.
-```kroki-mermaid
-classDiagram
- class SkillRegistry {
- +Skill Index
- +add(config_path) SkillRecord
- +upgrade(name, config_path) SkillRecord
- +remove(name) void
- +lookup(name) SkillRecord
- +list(filters) SkillRecord[]
- +tools(name) ToolDescriptor[]
- +validate_plan(plan) ValidationResult
- +refresh(name) void
- }
- class SkillRecord {
- +name : str
- +description : str
- +tags : list
- +config_path : str
- +includes : list~skill_name~
- +tool_refs : list~tool_name~
- +anonymous_tools : list~ToolDef~
- +flattened_tools : list~ToolDescriptor~
- +overrides : dict
- +capability_summary : dict
- }
- SkillRegistry --> SkillRecord : indexes
- note for SkillRegistry "Populated by:\n- agents skill add CLI command\n- Dynamic refresh on MCP notifications\n\nDepends on:\n- Tool Registry (resolve named tool references)\n\nConsumed by:\n- Actor activation\n- Plan validation\n- Permission enforcement\n- Agent context injection"
+```kroki-plantuml
+@startuml
+skinparam classAttributeIconSize 0
+skinparam classFontSize 13
+skinparam defaultFontSize 12
+
+class SkillRegistry {
+ - skillIndex : Map
+ --
+ + add(config_path) : SkillRecord
+ + upgrade(name, config_path) : SkillRecord
+ + remove(name) : void
+ + lookup(name) : SkillRecord
+ + list(filters) : SkillRecord[]
+ + tools(name) : ToolDescriptor[]
+ + validate_plan(plan) : ValidationResult
+ + refresh(name) : void
+}
+
+class SkillRecord {
+ + name : String
+ + description : String
+ + tags : List
+ + config_path : String
+ + includes : List
+ + tool_refs : List
+ + anonymous_tools : List
+ + flattened_tools : List
+ + overrides : Map
+ + capability_summary : CapabilitySummary
+}
+
+SkillRegistry "1" *-- "0..*" SkillRecord : indexes >
+
+note right of SkillRegistry
+ **Populated by:**
+ - agents skill add CLI command
+ - Dynamic refresh on MCP notifications
+
+ **Depends on:**
+ - Tool Registry (resolve named tool references)
+
+ **Consumed by:**
+ - Actor activation
+ - Plan validation
+ - Permission enforcement
+ - Agent context injection
+end note
+@enduml
```
Both registries persist in the database (local SQLite or server). MCP server tools are refreshed dynamically when `notifications/tools/list_changed` events are received.
@@ -7062,68 +7239,166 @@ Virtual types use simple names that mirror their physical counterparts. A virtua
The parent-child relationships between built-in resource types form three layers — git structure, git checkout (composition), and filesystem — bridged by virtual identity types:
-```kroki-mermaid
-flowchart TD
- subgraph GC["GIT-CHECKOUT (composition)"]
- gc["git-checkout\n/home/user/projects/myapp"]
- end
+```kroki-plantuml
+@startuml
+skinparam defaultFontSize 11
+skinparam objectFontSize 11
+skinparam packageStyle rectangle
- gc --> git["git\n(repo object DB)"]
- gc --> fsroot["fs-directory\n(worktree root)"]
+package "GIT-CHECKOUT (composition)" as GC #LightBlue {
+ object "git-checkout" as gc {
+ /home/user/projects/myapp
+ }
+}
- subgraph SFS["STANDALONE FS-DIRECTORY"]
- sfs["fs-directory\n(/opt/deploy/myapp)"]
- end
+package "GIT STRUCTURE" as GS #LightYellow {
+ object "git" as git {
+ repo object DB
+ }
+ object "git-remote" as remote {
+ origin
+ }
+ object "git-tag" as tag {
+ v1.0.0
+ }
+ object "git-submodule" as submod {
+ lib/shared
+ }
+ object "git-branch" as branch {
+ main
+ }
+ object "git-stash" as stash {
+ stash@0
+ }
+ object "git-commit" as commit {
+ a1b2c3d
+ }
+ object "git-tree" as tree {
+ e8f1... root
+ }
+ object "git-tree-entry" as entry1 {
+ README.md
+ }
+ object "git-tree" as subtree {
+ src/ subtree
+ }
+ object "git-tree-entry" as entry2 {
+ src/app.ts
+ }
+ object "git-tree-entry" as entry3 {
+ src/main.ts
+ }
+}
- subgraph SM["STANDALONE FS-MOUNT"]
- mount["fs-mount\n(/mnt/data)"]
- end
+package "FILESYSTEM (worktree)" as FS #LightGreen {
+ object "fs-directory" as fsroot {
+ worktree root
+ }
+ object "fs-directory" as srcdir {
+ src/
+ }
+ object "fs-file" as readme {
+ README.md
+ }
+ object "fs-file" as app {
+ app.ts
+ }
+ object "fs-file" as main {
+ main.ts
+ }
+}
- mount --> mountroot["fs-directory\n(root: /)"]
+package "STANDALONE FS-DIRECTORY" as SFS #Wheat {
+ object "fs-directory" as sfs {
+ /opt/deploy/myapp
+ }
+ object "fs-directory" as ssrcdir {
+ src/
+ }
+ object "fs-file" as sreadme {
+ README.md
+ }
+ object "fs-file" as sapp {
+ app.ts
+ }
+ object "fs-file" as smain {
+ main.ts
+ }
+ object "fs-symlink" as symlink {
+ link.txt
+ }
+}
- git --> remote["git-remote\n(origin)"]
- git --> tag["git-tag\n(v1.0.0)"]
- git --> submod["git-submodule\n(lib/shared)"]
- git --> branch["git-branch\n(main)"]
- git --> stash["git-stash\n(stash@0)"]
+package "STANDALONE FS-MOUNT" as SM #LightCoral {
+ object "fs-mount" as mount {
+ /mnt/data
+ }
+ object "fs-directory" as mountroot {
+ root: /
+ }
+}
- branch --> commit["git-commit\n(a1b2c3d)"]
- commit --> tree["git-tree\n(e8f1... root)"]
- tree --> entry1["git-tree-entry\nREADME.md"]
- tree --> subtree["git-tree\n(src/ subtree)"]
- subtree --> entry2["git-tree-entry\nsrc/app.ts"]
- subtree --> entry3["git-tree-entry\nsrc/main.ts"]
+package "VIRTUAL LAYER (abstract identities)" as VL #Lavender {
+ object "file" as vfile {
+ app.ts @ sha256:9f8e...
+ }
+ object "directory" as vdir {
+ src/ @ merkle:3d4f...
+ }
+ object "commit" as vcommit {
+ a1b2c3d
+ }
+ object "branch" as vbranch {
+ main @ a1b2c3d
+ }
+ object "remote" as vremote {
+ github.com/org/repo
+ }
+ object "tree" as vtree {
+ e8f1...9d2a
+ }
+}
- fsroot --> srcdir["fs-directory\n(src/)"]
- fsroot --> readme["fs-file\nREADME.md"]
- srcdir --> app["fs-file\napp.ts"]
- srcdir --> main["fs-file\nmain.ts"]
+gc --> git
+gc --> fsroot
- sfs --> ssrcdir["fs-directory\n(src/)"]
- sfs --> sreadme["fs-file\nREADME.md"]
- ssrcdir --> sapp["fs-file\napp.ts"]
- ssrcdir --> smain["fs-file\nmain.ts"]
- sfs --> symlink["fs-symlink\nlink.txt"]
+mount --> mountroot
- subgraph VL["VIRTUAL LAYER (abstract identities)"]
- vfile["file (app.ts @ sha256:9f8e...)\nchildren:\n- fs-file (worktree: src/app.ts)\n- fs-file (deploy: src/app.ts)\n- git-tree-entry (main:a1b:app.ts)"]
- vdir["directory (src/ @ merkle:3d4f...)\nchildren:\n- fs-directory (worktree: src/)\n- fs-directory (deploy: src/)\n- git-tree (main:a1b:src/)"]
- vcommit["commit (a1b2c3d)\nchildren:\n- git-commit (local/app)\n- git-commit (upstream)"]
- vbranch["branch (main @ a1b2c3d)\nchildren:\n- git-branch (local/app)\n- git-branch (upstream)"]
- vremote["remote (github.com/org/repo)\nchildren:\n- git-remote (local/app:origin)\n- git-remote (upstream:origin)"]
- vtree["tree (e8f1...9d2a)\nchildren:\n- git-tree (local/app)\n- git-tree (upstream)"]
- end
+git --> remote
+git --> tag
+git --> submod
+git --> branch
+git --> stash
- app -.-> vfile
- sapp -.-> vfile
- entry2 -.-> vfile
- srcdir -.-> vdir
- ssrcdir -.-> vdir
- subtree -.-> vdir
- commit -.-> vcommit
- branch -.-> vbranch
- remote -.-> vremote
- tree -.-> vtree
+branch --> commit
+commit --> tree
+tree --> entry1
+tree --> subtree
+subtree --> entry2
+subtree --> entry3
+
+fsroot --> srcdir
+fsroot --> readme
+srcdir --> app
+srcdir --> main
+
+sfs --> ssrcdir
+sfs --> sreadme
+sfs --> symlink
+ssrcdir --> sapp
+ssrcdir --> smain
+
+app ..> vfile : equivalent
+sapp ..> vfile : equivalent
+entry2 ..> vfile : equivalent
+srcdir ..> vdir : equivalent
+ssrcdir ..> vdir : equivalent
+subtree ..> vdir : equivalent
+commit ..> vcommit : equivalent
+branch ..> vbranch : equivalent
+remote ..> vremote : equivalent
+tree ..> vtree : equivalent
+@enduml
```
**Reading the diagram:**
@@ -7177,60 +7452,58 @@ agents resource add fs-directory local/acme-deploy \
**`local/acme-app`** (type: `git-checkout`) discovers two children — a `git` and an `fs-directory` (the worktree root):
-```kroki-mermaid
-flowchart TD
- root["local/acme-app\ngit-checkout / physical"]
- root --> repo["local/acme-app:repo\ngit / physical"]
- root --> wt["local/acme-app:worktree\nfs-directory / physical\n(/home/alice/projects/acme-dashboard/)"]
-
- repo --> origin["acme-app:repo:origin\ngit-remote\ngit@github.com:acmecorp/dashboard.git"]
- repo --> v100["acme-app:repo:v1.0.0\ngit-tag"]
- repo --> submod["acme-app:repo:lib/shared\ngit-submodule\n@ c4d5e6f"]
- repo --> stash0["acme-app:repo:stash@0\ngit-stash"]
- repo --> mainBr["acme-app:repo:main\ngit-branch"]
- repo --> devBr["acme-app:repo:develop\ngit-branch"]
-
- mainBr --> mainCommit["main:a7f3e21\ngit-commit"]
- mainCommit --> mainTree["main:a7f3e21:tree\ngit-tree (root)"]
- mainTree --> mainReadme["a7f3e21:README.md\ngit-tree-entry"]
- mainTree --> mainPkg["a7f3e21:package.json\ngit-tree-entry"]
- mainTree --> mainSrc["a7f3e21:src/\ngit-tree (subtree)"]
- mainSrc --> mainApp["a7f3e21:src/app.ts\ngit-tree-entry"]
- mainSrc --> mainApi["a7f3e21:src/api.ts\ngit-tree-entry"]
- mainSrc --> mainUtils["a7f3e21:src/utils.ts\ngit-tree-entry"]
-
- devBr --> devCommit["develop:b2c4d8e\ngit-commit"]
- devCommit --> devTree["develop:b2c4d8e:tree\ngit-tree"]
- devTree --> devSrc["b2c4d8e:src/\ngit-tree"]
- devSrc --> devApp["b2c4d8e:src/app.ts\ngit-tree-entry"]
- devSrc --> devApi["b2c4d8e:src/api.ts\ngit-tree-entry (modified)"]
-
- wt --> wtSrc["worktree:src/\nfs-directory"]
- wtSrc --> wtApp["worktree:src/app.ts\nfs-file"]
- wtSrc --> wtApi["worktree:src/api.ts\nfs-file"]
- wtSrc --> wtUtils["worktree:src/utils.ts\nfs-file"]
- wt --> wtPkg["worktree:package.json\nfs-file"]
- wt --> wtReadme["worktree:README.md\nfs-file"]
- wt --> wtDocs["worktree:docs → ../docs\nfs-symlink"]
+```kroki-plantuml
+@startwbs
+* local/acme-app\n(git-checkout / physical)
+** local/acme-app:repo\n(git / physical)
+*** acme-app:repo:origin\n(git-remote)\ngit@github.com:acmecorp/dashboard.git
+*** acme-app:repo:v1.0.0\n(git-tag)
+*** acme-app:repo:lib/shared\n(git-submodule @ c4d5e6f)
+*** acme-app:repo:stash@0\n(git-stash)
+*** acme-app:repo:main\n(git-branch)
+**** main:a7f3e21\n(git-commit)
+***** main:a7f3e21:tree\n(git-tree / root)
+****** a7f3e21:README.md\n(git-tree-entry)
+****** a7f3e21:package.json\n(git-tree-entry)
+****** a7f3e21:src/\n(git-tree / subtree)
+******* a7f3e21:src/app.ts\n(git-tree-entry)
+******* a7f3e21:src/api.ts\n(git-tree-entry)
+******* a7f3e21:src/utils.ts\n(git-tree-entry)
+*** acme-app:repo:develop\n(git-branch)
+**** develop:b2c4d8e\n(git-commit)
+***** develop:b2c4d8e:tree\n(git-tree)
+****** b2c4d8e:src/\n(git-tree)
+******* b2c4d8e:src/app.ts\n(git-tree-entry)
+******* b2c4d8e:src/api.ts\n(git-tree-entry / modified)
+** local/acme-app:worktree\n(fs-directory / physical)\n/home/alice/projects/acme-dashboard/
+*** worktree:src/\n(fs-directory)
+**** worktree:src/app.ts\n(fs-file)
+**** worktree:src/api.ts\n(fs-file)
+**** worktree:src/utils.ts\n(fs-file)
+*** worktree:package.json\n(fs-file)
+*** worktree:README.md\n(fs-file)
+*** worktree:docs -> ../docs\n(fs-symlink)
+@endwbs
```
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:
-```kroki-mermaid
-flowchart TD
- root["local/acme-upstream\ngit / physical\n(git@github.com:acmecorp/dashboard.git)"]
- root --> origin["acme-upstream:origin\ngit-remote"]
- root --> v100["acme-upstream:v1.0.0\ngit-tag"]
- root --> mainBr["acme-upstream:main\ngit-branch"]
- root --> devBr["acme-upstream:develop\ngit-branch"]
- mainBr --> mainCommit["main:a7f3e21\ngit-commit"]
- mainCommit --> mainTree["main:a7f3e21:tree\ngit-tree"]
- mainTree --> mainSrc["a7f3e21:src/\ngit-tree"]
- mainSrc --> mainApp["a7f3e21:src/app.ts\ngit-tree-entry"]
- mainSrc --> mainApi["a7f3e21:src/api.ts\ngit-tree-entry"]
- devBr --> devCommit["...\n(remaining structure)"]
+```kroki-plantuml
+@startwbs
+* local/acme-upstream\n(git / physical)\ngit@github.com:acmecorp/dashboard.git
+** acme-upstream:origin\n(git-remote)
+** acme-upstream:v1.0.0\n(git-tag)
+** acme-upstream:main\n(git-branch)
+*** main:a7f3e21\n(git-commit)
+**** main:a7f3e21:tree\n(git-tree)
+***** a7f3e21:src/\n(git-tree)
+****** a7f3e21:src/app.ts\n(git-tree-entry)
+****** a7f3e21:src/api.ts\n(git-tree-entry)
+** acme-upstream:develop\n(git-branch)
+*** ...\n(remaining structure)
+@endwbs
```
A standalone `git` resource has **full access to branches, tags, commits, trees, and tree entries** — everything in the git object database — but **no `fs-directory` child** and **no `fs-file` resources**. There are no files on the local disk (the repo exists on GitHub's servers). Tools that need local file access cannot bind to it.
@@ -7239,15 +7512,16 @@ This is the key difference from `git-checkout`: a `git` resource represents a sp
**`local/acme-deploy`** (type: `fs-directory`, standalone) discovers:
-```kroki-mermaid
-flowchart TD
- root["local/acme-deploy\nfs-directory / physical\n(/opt/deploy/acme-dashboard/)"]
- root --> src["acme-deploy:src/\nfs-directory"]
- src --> app["acme-deploy:src/app.ts\nfs-file"]
- src --> api["acme-deploy:src/api.ts\nfs-file"]
- src --> utils["acme-deploy:src/utils.ts\nfs-file"]
- root --> pkg["acme-deploy:package.json\nfs-file"]
- root --> readme["acme-deploy:README.md\nfs-file"]
+```kroki-plantuml
+@startwbs
+* local/acme-deploy\n(fs-directory / physical)\n/opt/deploy/acme-dashboard/
+** acme-deploy:src/\n(fs-directory)
+*** acme-deploy:src/app.ts\n(fs-file)
+*** acme-deploy:src/api.ts\n(fs-file)
+*** acme-deploy:src/utils.ts\n(fs-file)
+** acme-deploy:package.json\n(fs-file)
+** acme-deploy:README.md\n(fs-file)
+@endwbs
```
A standalone `fs-directory` — no git metadata, no branches, no commits, no tree entries. Just a directory containing files and subdirectories. Uses the **same `fs-directory` and `fs-file` types** as the git checkout's worktree root.
@@ -7256,58 +7530,175 @@ A standalone `fs-directory` — no git metadata, no branches, no commits, no tre
After all three resources are registered, the system detects equivalent physical resources and creates virtual parents to link them:
-```kroki-mermaid
-flowchart LR
- subgraph VL["VIRTUAL LAYER (auto-created by equivalence matching)"]
- vDir["directory: src/\n(merkle:3d4f...a2b1)"]
- vAppTs["file: app.ts\n(sha256:9f8e...c4d5)"]
- vUtilsTs["file: utils.ts\n(sha256:a2b1...7f4c)"]
- vApiTs["file: api.ts\n(sha256:e1d3...8a9b)"]
- vCommit["commit: a7f3e21"]
- vBranch["branch: main @ a7f3e21"]
- vTag["tag: v1.0.0 → a7f3e21"]
- vRemote["remote:\ngit@github.com:acmecorp/dashboard.git"]
- vTree["tree: e8f1...9d2a"]
- vSubmod["submodule: lib/shared"]
- end
+```kroki-plantuml
+@startuml
+skinparam defaultFontSize 10
+skinparam objectFontSize 10
+skinparam packageStyle rectangle
+left to right direction
- acmeWtSrc["acme-app:worktree:src/ (fs-directory)"] -.-> vDir
- deploySrc["acme-deploy:src/ (fs-directory)"] -.-> vDir
- acmeGitSrc["acme-app:repo:main:a7f3e21:src/ (git-tree)"] -.-> vDir
- upstreamSrc["acme-upstream:main:a7f3e21:src/ (git-tree)"] -.-> vDir
+package "PHYSICAL: local/acme-app" as P1 #LightBlue {
+ object "fs-directory" as acmeWtSrc {
+ worktree:src/
+ }
+ object "fs-file" as acmeWtApp {
+ worktree:src/app.ts
+ }
+ object "fs-file" as acmeWtUtils {
+ worktree:src/utils.ts
+ }
+ object "fs-file" as acmeWtApi {
+ worktree:src/api.ts
+ }
+ object "git-tree" as acmeGitSrc {
+ main:a7f3e21:src/
+ }
+ object "git-tree-entry" as acmeGitApp {
+ main:src/app.ts
+ }
+ object "git-tree-entry" as acmeGitUtils {
+ main:src/utils.ts
+ }
+ object "git-tree-entry" as acmeGitApi {
+ main:src/api.ts
+ }
+ object "git-commit" as acmeCommit {
+ main:a7f3e21
+ }
+ object "git-branch" as acmeBranch {
+ main
+ }
+ object "git-tag" as acmeTag {
+ v1.0.0
+ }
+ object "git-remote" as acmeRemote {
+ origin
+ }
+ object "git-tree" as acmeTree {
+ main:a7f3e21:tree
+ }
+ object "git-submodule" as acmeSubmod {
+ lib/shared
+ }
+}
- acmeWtApp["acme-app:worktree:src/app.ts (fs-file)"] -.-> vAppTs
- deployApp["acme-deploy:src/app.ts (fs-file)"] -.-> vAppTs
- acmeGitApp["acme-app:repo:main:src/app.ts (git-tree-entry)"] -.-> vAppTs
- upstreamApp["acme-upstream:main:src/app.ts (git-tree-entry)"] -.-> vAppTs
+package "PHYSICAL: local/acme-deploy" as P2 #LightGreen {
+ object "fs-directory" as deploySrc {
+ src/
+ }
+ object "fs-file" as deployApp {
+ src/app.ts
+ }
+ object "fs-file" as deployUtils {
+ src/utils.ts
+ }
+ object "fs-file" as deployApi {
+ src/api.ts
+ }
+}
- acmeWtUtils["acme-app:worktree:src/utils.ts (fs-file)"] -.-> vUtilsTs
- deployUtils["acme-deploy:src/utils.ts (fs-file)"] -.-> vUtilsTs
- acmeGitUtils["acme-app:repo:main:src/utils.ts (git-tree-entry)"] -.-> vUtilsTs
- upstreamUtils["acme-upstream:main:src/utils.ts (git-tree-entry)"] -.-> vUtilsTs
+package "PHYSICAL: local/acme-upstream" as P3 #LightYellow {
+ object "git-tree" as upstreamSrc {
+ main:a7f3e21:src/
+ }
+ object "git-tree-entry" as upstreamApp {
+ main:src/app.ts
+ }
+ object "git-tree-entry" as upstreamUtils {
+ main:src/utils.ts
+ }
+ object "git-tree-entry" as upstreamApi {
+ main:src/api.ts
+ }
+ object "git-commit" as upstreamCommit {
+ main:a7f3e21
+ }
+ object "git-branch" as upstreamBranch {
+ main
+ }
+ object "git-tag" as upstreamTag {
+ v1.0.0
+ }
+ object "git-remote" as upstreamRemote {
+ origin
+ }
+ object "git-tree" as upstreamTree {
+ main:a7f3e21:tree
+ }
+}
- acmeWtApi["acme-app:worktree:src/api.ts (fs-file)"] -.-> vApiTs
- deployApi["acme-deploy:src/api.ts (fs-file)"] -.-> vApiTs
- acmeGitApi["acme-app:repo:main:src/api.ts (git-tree-entry)"] -.-> vApiTs
- upstreamApi["acme-upstream:main:src/api.ts (git-tree-entry)"] -.-> vApiTs
- note1["NOT linked: develop:b2c4d8e:src/api.ts\n(different content on develop branch)"]
+package "VIRTUAL LAYER\n(auto-created by equivalence)" as VL #Lavender {
+ object "directory" as vDir {
+ src/ (merkle:3d4f...)
+ }
+ object "file" as vAppTs {
+ app.ts (sha256:9f8e...)
+ }
+ object "file" as vUtilsTs {
+ utils.ts (sha256:a2b1...)
+ }
+ object "file" as vApiTs {
+ api.ts (sha256:e1d3...)
+ }
+ object "commit" as vCommit {
+ a7f3e21
+ }
+ object "branch" as vBranch {
+ main @ a7f3e21
+ }
+ object "tag" as vTag {
+ v1.0.0
+ }
+ object "remote" as vRemote {
+ github.com:acmecorp/dashboard.git
+ }
+ object "tree" as vTree {
+ e8f1...9d2a
+ }
+ object "submodule" as vSubmod {
+ lib/shared
+ }
+}
- acmeCommit["acme-app:repo:main:a7f3e21 (git-commit)"] -.-> vCommit
- upstreamCommit["acme-upstream:main:a7f3e21 (git-commit)"] -.-> vCommit
+acmeWtSrc ..> vDir
+deploySrc ..> vDir
+acmeGitSrc ..> vDir
+upstreamSrc ..> vDir
- acmeBranch["acme-app:repo:main (git-branch)"] -.-> vBranch
- upstreamBranch["acme-upstream:main (git-branch)"] -.-> vBranch
+acmeWtApp ..> vAppTs
+deployApp ..> vAppTs
+acmeGitApp ..> vAppTs
+upstreamApp ..> vAppTs
- acmeTag["acme-app:repo:v1.0.0 (git-tag)"] -.-> vTag
- upstreamTag["acme-upstream:v1.0.0 (git-tag)"] -.-> vTag
+acmeWtUtils ..> vUtilsTs
+deployUtils ..> vUtilsTs
+acmeGitUtils ..> vUtilsTs
+upstreamUtils ..> vUtilsTs
- acmeRemote["acme-app:repo:origin (git-remote)"] -.-> vRemote
- upstreamRemote["acme-upstream:origin (git-remote)"] -.-> vRemote
+acmeWtApi ..> vApiTs
+deployApi ..> vApiTs
+acmeGitApi ..> vApiTs
+upstreamApi ..> vApiTs
- acmeTree["acme-app:repo:main:a7f3e21:tree (git-tree)"] -.-> vTree
- upstreamTree["acme-upstream:main:a7f3e21:tree (git-tree)"] -.-> vTree
+note "NOT linked: develop:b2c4d8e:src/api.ts\n(different content on develop branch)" as N1
- acmeSubmod["acme-app:repo:lib/shared (git-submodule)"] -.-> vSubmod
+acmeCommit ..> vCommit
+upstreamCommit ..> vCommit
+
+acmeBranch ..> vBranch
+upstreamBranch ..> vBranch
+
+acmeTag ..> vTag
+upstreamTag ..> vTag
+
+acmeRemote ..> vRemote
+upstreamRemote ..> vRemote
+
+acmeTree ..> vTree
+upstreamTree ..> vTree
+
+acmeSubmod ..> vSubmod
+@enduml
```
**How the `directory` virtual type works:**
@@ -7405,81 +7796,118 @@ The `user_addable` field determines whether the type appears as a subcommand. Ty
Resources form a **directed acyclic graph** (DAG), not a simple tree. A resource can have **multiple parents** and **multiple children**, subject to type constraints. The diagram below shows how a `git-checkout`, a standalone `git` repo (remote), a standalone `fs-directory`, and virtual resources interconnect:
-```kroki-mermaid
-flowchart TD
- subgraph GCO["GIT-CHECKOUT"]
- gco["git-checkout\nlocal/app"]
- end
+```kroki-plantuml
+@startuml
+skinparam defaultFontSize 10
+skinparam objectFontSize 10
+skinparam packageStyle rectangle
- subgraph GR["GIT (remote)"]
- gr["git\nlocal/upstream"]
- end
+package "GIT-CHECKOUT: local/app" as GCO #LightBlue {
+ object "git-checkout" as gco {
+ local/app
+ }
+ object "git" as gcoGit {
+ local/app:repo
+ }
+ object "git-remote" as gcoRemote { origin }
+ object "git-tag" as gcoTag { v1.0.0 }
+ object "git-branch" as gcoBranch { main }
+ object "git-commit" as gcoCommit { a1b2c3d }
+ object "git-tree" as gcoTree { e8f1... root }
+ object "git-tree-entry" as gcoEntry1 { README.md }
+ object "git-tree" as gcoSubtree { src/ }
+ object "git-tree-entry" as gcoEntry2 { src/app.ts }
+ object "git-tree-entry" as gcoEntry3 { src/main.ts }
+ object "fs-directory" as gcoFs { local/app:worktree }
+ object "fs-directory" as gcoSrcDir { src/ }
+ object "fs-file" as gcoReadme { README.md }
+ object "fs-file" as gcoApp { app.ts }
+ object "fs-file" as gcoMain { main.ts }
+}
- subgraph SFD["STANDALONE FS-DIRECTORY"]
- sfd["fs-directory\nlocal/deploy\n(/opt/deploy/myapp)"]
- end
+package "GIT (remote): local/upstream" as GR #LightYellow {
+ object "git" as gr { local/upstream }
+ object "git-remote" as grRemote { origin }
+ object "git-tag" as grTag { v1.0.0 }
+ object "git-branch" as grBranch { main }
+ object "git-commit" as grCommit { a1b2c3d }
+ object "git-tree" as grTree { e8f1... root }
+ object "git-tree-entry" as grEntry1 { README.md }
+ object "git-tree" as grSubtree { src/ }
+ object "git-tree-entry" as grEntry2 { src/app.ts }
+ object "git-tree-entry" as grEntry3 { src/main.ts }
+}
- gco --> gcoGit["git\nlocal/app:repo"]
- gco --> gcoFs["fs-directory\nlocal/app:worktree"]
+package "STANDALONE FS-DIRECTORY: local/deploy" as SFD #LightGreen {
+ object "fs-directory" as sfd { /opt/deploy/myapp }
+ object "fs-directory" as sfdSrcDir { src/ }
+ object "fs-file" as sfdReadme { README.md }
+ object "fs-file" as sfdApp { app.ts }
+ object "fs-file" as sfdMain { main.ts }
+}
- gcoGit --> gcoRemote["git-remote\n(origin)"]
- gcoGit --> gcoTag["git-tag\n(v1.0.0)"]
- gcoGit --> gcoBranch["git-branch\n(main)"]
- gcoBranch --> gcoCommit["git-commit\n(a1b2c3d)"]
- gcoCommit --> gcoTree["git-tree\n(e8f1... root)"]
- gcoTree --> gcoEntry1["git-tree-entry\n(README.md)"]
- gcoTree --> gcoSubtree["git-tree\n(src/)"]
- gcoSubtree --> gcoEntry2["git-tree-entry\n(src/app.ts)"]
- gcoSubtree --> gcoEntry3["git-tree-entry\n(src/main.ts)"]
+package "VIRTUAL LAYER" as VL #Lavender {
+ object "file" as vFile { app.ts@v1 }
+ object "directory" as vDir { src/@v1 }
+ object "commit" as vCommit { a1b2c3d }
+ object "branch" as vBranch { main@a1b... }
+ object "tag" as vTag { v1.0.0 }
+ object "remote" as vRemote { github.com/org/repo }
+ object "tree" as vTree { e8f1...9d2a }
+}
- gr --> grRemote["git-remote\n(origin)"]
- gr --> grTag["git-tag\n(v1.0.0)"]
- gr --> grBranch["git-branch\n(main)"]
- grBranch --> grCommit["git-commit\n(a1b2c3d)"]
- grCommit --> grTree["git-tree\n(e8f1... root)"]
- grTree --> grEntry1["git-tree-entry\n(README.md)"]
- grTree --> grSubtree["git-tree\n(src/)"]
- grSubtree --> grEntry2["git-tree-entry\n(src/app.ts)"]
- grSubtree --> grEntry3["git-tree-entry\n(src/main.ts)"]
+' Physical hierarchy
+gco --> gcoGit
+gco --> gcoFs
+gcoGit --> gcoRemote
+gcoGit --> gcoTag
+gcoGit --> gcoBranch
+gcoBranch --> gcoCommit
+gcoCommit --> gcoTree
+gcoTree --> gcoEntry1
+gcoTree --> gcoSubtree
+gcoSubtree --> gcoEntry2
+gcoSubtree --> gcoEntry3
+gcoFs --> gcoSrcDir
+gcoFs --> gcoReadme
+gcoSrcDir --> gcoApp
+gcoSrcDir --> gcoMain
- gcoFs --> gcoSrcDir["fs-directory\n(src/)"]
- gcoFs --> gcoReadme["fs-file\n(README.md)"]
- gcoSrcDir --> gcoApp["fs-file\n(app.ts)"]
- gcoSrcDir --> gcoMain["fs-file\n(main.ts)"]
+gr --> grRemote
+gr --> grTag
+gr --> grBranch
+grBranch --> grCommit
+grCommit --> grTree
+grTree --> grEntry1
+grTree --> grSubtree
+grSubtree --> grEntry2
+grSubtree --> grEntry3
- sfd --> sfdSrcDir["fs-directory\n(src/)"]
- sfd --> sfdReadme["fs-file\n(README.md)"]
- sfdSrcDir --> sfdApp["fs-file\n(app.ts)"]
- sfdSrcDir --> sfdMain["fs-file\n(main.ts)"]
+sfd --> sfdSrcDir
+sfd --> sfdReadme
+sfdSrcDir --> sfdApp
+sfdSrcDir --> sfdMain
- subgraph VL["VIRTUAL LAYER"]
- vFile["file (app.ts@v1)\nfs-file (wt) + fs-file (dpl)\n+ git-tree-entry (local)\n+ git-tree-entry (upstream)"]
- vDir["directory (src/@v1)\nfs-directory (wt) + fs-directory (dpl)\n+ git-tree (local) + git-tree (upstream)"]
- vCommit["commit (a1b2c3d)\ngit-commit (local)\n+ git-commit (upstream)"]
- vBranch["branch (main@a1b...)\ngit-branch (local)\n+ git-branch (upstream)"]
- vTag["tag (v1.0.0)\ngit-tag (local)\n+ git-tag (upstream)"]
- vRemote["remote (github.com/org/repo)\ngit-remote (local)\n+ git-remote (upstream)"]
- vTree["tree (e8f1...9d2a)\ngit-tree (local)\n+ git-tree (upstream)"]
- end
-
- gcoApp -.-> vFile
- sfdApp -.-> vFile
- gcoEntry2 -.-> vFile
- grEntry2 -.-> vFile
- gcoSrcDir -.-> vDir
- sfdSrcDir -.-> vDir
- gcoSubtree -.-> vDir
- grSubtree -.-> vDir
- gcoCommit -.-> vCommit
- grCommit -.-> vCommit
- gcoBranch -.-> vBranch
- grBranch -.-> vBranch
- gcoTag -.-> vTag
- grTag -.-> vTag
- gcoRemote -.-> vRemote
- grRemote -.-> vRemote
- gcoTree -.-> vTree
- grTree -.-> vTree
+' Virtual equivalence links
+gcoApp ..> vFile
+sfdApp ..> vFile
+gcoEntry2 ..> vFile
+grEntry2 ..> vFile
+gcoSrcDir ..> vDir
+sfdSrcDir ..> vDir
+gcoSubtree ..> vDir
+grSubtree ..> vDir
+gcoCommit ..> vCommit
+grCommit ..> vCommit
+gcoBranch ..> vBranch
+grBranch ..> vBranch
+gcoTag ..> vTag
+grTag ..> vTag
+gcoRemote ..> vRemote
+grRemote ..> vRemote
+gcoTree ..> vTree
+grTree ..> vTree
+@enduml
```
**Key properties of the DAG:**
@@ -7604,53 +8032,82 @@ These capabilities are used by the **tool execution flow** to validate that a to
CleverAgents maintains a **Resource Registry** — a persistent catalog of all registered resources and their DAG relationships:
-```kroki-mermaid
-classDiagram
- class ResourceRegistry {
- +Resource Index
- +Resource Type Index
- +add(type, name, properties) ResourceRecord
- +upgrade(name, properties) ResourceRecord
- +remove(name) void
- +lookup(name) ResourceRecord
- +list(filters) ResourceRecord[]
- +tree(name, depth) DAG_subtree
- +link_child(parent, child) void
- +unlink_child(parent, child) void
- +refresh(name) void
- +find_by_content(hash) ResourceRecord[]
- +find_virtual_parent(resource) ResourceRecord
- }
- class ResourceRecord {
- +name : str
- +type : str
- +physical_or_virtual : enum
- +properties : dict
- +capabilities : dict
- +parents : list
- +children : list
- +content_hash : str
- +linked_projects : list
- +created_at : datetime
- +updated_at : datetime
- }
- class ResourceTypeRecord {
- +name : str
- +description : str
- +source : str
- +physical_or_virtual : enum
- +user_addable : bool
- +cli_arguments : list
- +allowed_parent_types : list
- +child_types : dict
- +sandbox_strategy : str
- +handler : str
- +capabilities : dict
- +config_path : str
- }
- ResourceRegistry --> ResourceRecord : indexes resources
- ResourceRegistry --> ResourceTypeRecord : indexes types
- note for ResourceRegistry "Populated by:\n- agents resource add CLI\n- Auto-discovery during registration\n- Content-identity linking\n\nConsumed by:\n- Tool binding resolution\n- Sandbox creation\n- Change tracking\n- Project linking\n- Plan validation"
+```kroki-plantuml
+@startuml
+skinparam classAttributeIconSize 0
+skinparam classFontSize 13
+skinparam defaultFontSize 12
+
+class ResourceRegistry {
+ - resourceIndex : Map
+ - typeIndex : Map
+ --
+ + add(type, name, properties) : ResourceRecord
+ + upgrade(name, properties) : ResourceRecord
+ + remove(name) : void
+ + lookup(name) : ResourceRecord
+ + list(filters) : ResourceRecord[]
+ + tree(name, depth) : DAG_subtree
+ + link_child(parent, child) : void
+ + unlink_child(parent, child) : void
+ + refresh(name) : void
+ + find_by_content(hash) : ResourceRecord[]
+ + find_virtual_parent(resource) : ResourceRecord
+}
+
+class ResourceRecord {
+ + name : String
+ + type : String
+ + physical_or_virtual : PhysVirt
+ + properties : Map
+ + capabilities : Capabilities
+ + parents : List
+ + children : List
+ + content_hash : String
+ + linked_projects : List
+ + created_at : DateTime
+ + updated_at : DateTime
+}
+
+class ResourceTypeRecord {
+ + name : String
+ + description : String
+ + source : String
+ + physical_or_virtual : PhysVirt
+ + user_addable : Boolean
+ + cli_arguments : List
+ + allowed_parent_types : List
+ + child_types : Map
+ + sandbox_strategy : String
+ + handler : String
+ + capabilities : Capabilities
+ + config_path : String
+}
+
+enum PhysVirt {
+ physical
+ virtual
+}
+
+ResourceRegistry "1" *-- "0..*" ResourceRecord : indexes resources >
+ResourceRegistry "1" *-- "0..*" ResourceTypeRecord : indexes types >
+ResourceRecord --> PhysVirt
+ResourceTypeRecord --> PhysVirt
+
+note right of ResourceRegistry
+ **Populated by:**
+ - agents resource add CLI
+ - Auto-discovery during registration
+ - Content-identity linking
+
+ **Consumed by:**
+ - Tool binding resolution
+ - Sandbox creation
+ - Change tracking
+ - Project linking
+ - Plan validation
+end note
+@enduml
```
The Resource Registry persists in the database (local SQLite or server). It works alongside the Tool Registry and Skill Registry.
@@ -7783,10 +8240,20 @@ Additional handlers are provided by custom resource types when they are register
Paths in tool invocations are resolved through a resource routing system that uses tool-resource bindings to determine the target resource:
```kroki-mermaid
-flowchart TD
- A["Tool invocation:\nedit_file(path='src/main.py', ...)"] --> B
- B["Resource Router\n1. Check tool's resource bindings\n → slot 'repo' bound to local/api-repo (git-checkout)\n2. Resolve path within bound resource\n → local/api-repo:worktree:src/main.py\n3. Route to resource handler\n → GitCheckoutHandler"] --> C
- C["GitCheckoutHandler\n- Resolves path to sandbox worktree\n- Operates on sandboxed state\n- Returns Change record"]
+sequenceDiagram
+ participant Tool as Tool Invocation
+ participant Router as Resource Router
+ participant Handler as GitCheckoutHandler
+
+ Tool->>Router: edit_file(path='src/main.py', ...)
+ Router->>Router: Check tool's resource bindings
+ Note right of Router: slot 'repo' bound to
local/api-repo (git-checkout)
+ Router->>Router: Resolve path within bound resource
+ Note right of Router: local/api-repo:worktree:src/main.py
+ Router->>Handler: Route to resource handler
+ Handler->>Handler: Resolve path to sandbox worktree
+ Handler->>Handler: Operate on sandboxed state
+ Handler-->>Tool: Return Change record
```
When a tool has multiple resource slots bound, the path scheme or slot name disambiguates: