diff --git a/docs/specification.md b/docs/specification.md index 0ff4d772d..b96c99064 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -4142,19 +4142,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. -``` -Plan Tree Example: -├── [prompt_definition] "Increase test coverage to 85%" <- Root decision (correctable) -│ ├── [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" <- Created by parent -│ │ ├── [implementation_choice] "Test login flow first" -│ │ └── ... -│ └── [subplan_spawn] "Write tests for payment module" -│ └── Subplan: payment-tests -│ ├── [prompt_definition] "Write unit tests for payment module" <- Created by parent -│ └── ... +```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["..."] ``` ##### Correcting Decisions (Including Prompts) @@ -4886,36 +4885,13 @@ This architecture provides: ##### How It Works -``` -LLM Response (with tool calls) - ↓ -┌─────────────────────────────────────┐ -│ Tool Router │ -│ - Routes each tool call to handler │ -│ - Validates parameters │ -│ - Enforces capability restrictions │ -└─────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────┐ -│ Sandbox Execution │ -│ - Tool operates on sandboxed state │ -│ - Each invocation recorded │ -│ - Checkpoint created if needed │ -└─────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────┐ -│ ChangeSet Accumulation │ -│ - Each resource-modifying call → │ -│ becomes a Change record │ -│ - ChangeSet = history of changes │ -└─────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────┐ -│ Validation & Review │ -│ - Run validators on sandbox state │ -│ - Generate diff from ChangeSet │ -│ - Present for review before Apply │ -└─────────────────────────────────────┘ +```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"] ``` ##### Built-in Resource Tools @@ -5601,21 +5577,16 @@ 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. -``` -┌──────────────────────────────────────────────────────────────────┐ -│ Tool: Dual Role │ -│ │ -│ ┌─────────────────────────┐ ┌──────────────────────────┐ │ -│ │ Role 1: In a Skill │ │ Role 2: In an Actor Graph│ │ -│ │ │ │ │ │ -│ │ Skill: local/devops │ │ Actor Graph: │ │ -│ │ tools: │ │ nodes: │ │ -│ │ - local/run-migr.. │ │ - name: run_db │ │ -│ │ - local/validate.. │ │ type: tool │ │ -│ │ (tool-calling by LLM) │ │ tool: local/run-.. │ │ -│ └─────────────────────────┘ │ (deterministic invoke) │ │ -│ └──────────────────────────┘ │ -└──────────────────────────────────────────────────────────────────┘ +```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 + end ``` #### Tool Configuration (YAML) @@ -5988,40 +5959,27 @@ The `from_param` field links a resource slot to an input parameter. At invocatio ##### Binding Resolution Flow -``` -Tool activation (actor references skill or tool node) - │ - ├─→ For each resource slot in the tool: - │ │ - │ ├─ Static binding (has `bind` field)? - │ │ └─→ Resolve from Resource Registry by name - │ │ └─→ Validate type compatibility - │ │ - │ ├─ Contextual binding (no `bind`, no `from_param`)? - │ │ └─→ Search plan's project linked resources - │ │ ├─→ Filter by resource type - │ │ ├─→ If one match → auto-bind - │ │ ├─→ If multiple → try alias/name match, else error - │ │ └─→ If none → validation error - │ │ - │ └─ Parameter binding (has `from_param`)? - │ └─→ Defer to invocation time - │ - └─→ Store resolved bindings in ToolActivationContext +```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 -Tool invocation - │ - ├─→ For parameter-bound slots: - │ └─→ Resolve resource name from params - │ └─→ Validate type + access compatibility - │ - ├─→ For all bound resources: - │ ├─→ Ensure sandbox exists (lazy sandboxing) - │ ├─→ Validate access mode (read_only tool on writable resource OK; - │ │ read_write tool on read_only resource → error) - │ └─→ Inject into ToolExecutionContext as ctx.resources[slot_name] - │ - └─→ Execute tool with bound resources available + 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 ``` ##### Built-in Tool Resource Bindings @@ -6050,42 +6008,30 @@ The binding system enables powerful resource discovery queries: CleverAgents maintains a **Tool Registry** — a persistent catalog of all independently registered tools: -``` -┌──────────────────────────────────────────────────────────┐ -│ Tool Registry │ -│ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Tool Index │ │ -│ │ │ │ -│ │ tool_name → ToolRecord { │ │ -│ │ name, description, tags, source, config_path, │ │ -│ │ input_schema, output_schema, │ │ -│ │ capability_metadata, │ │ -│ │ resource_slots: [ResourceSlot, ...], │ │ -│ │ code (for custom tools) │ │ -│ │ } │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -│ Operations: │ -│ add(config_path) → ToolRecord │ -│ upgrade(name, config_path) → ToolRecord │ -│ remove(name) → void │ -│ lookup(name) → ToolRecord │ -│ list(filters) → ToolRecord[] │ -│ │ -│ Populated by: │ -│ • agents tool add [--upgrade] CLI command │ -│ • Dynamic refresh on MCP notifications │ -│ │ -│ Consumed by: │ -│ • Skill registration (resolve tool references) │ -│ • Actor graph construction (resolve tool node refs) │ -│ • Resource binding resolution (match slots to │ -│ project resources) │ -│ • Plan validation ("does this tool exist?") │ -│ • Permission enforcement │ -│ │ -└──────────────────────────────────────────────────────────┘ +```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" ``` 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. @@ -6094,48 +6040,66 @@ 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: -``` -┌──────────────────────────────────────────────────────────┐ -│ Tool │ -│ │ -│ identity: │ -│ name : str # namespaced (registered) │ -│ or generated (anonymous)│ -│ qualified_name: str # / │ -│ source : enum # mcp | agent_skill | │ -│ builtin | custom │ -│ │ -│ schema: │ -│ input_schema : JSONSchema # parameters the tool │ -│ accepts │ -│ output_schema : JSONSchema # optional structured │ -│ return type │ -│ │ -│ capability_metadata: # see "Capability │ -│ Metadata" below │ -│ read_only, writes, write_scope, idempotent, │ -│ checkpointable, side_effects, required_permissions, │ -│ cost_profile, human_approval_required │ -│ │ -│ resource_bindings: # see "Resource │ -│ Bindings" above │ -│ slots: dict[str, ResourceSlot] # named slots │ -│ ResourceSlot: {type, access, required, bind?, │ -│ from_param?, description} │ -│ │ -│ lifecycle: │ -│ discover() → ToolDescriptor # metadata only │ -│ activate() → void # connect / load │ -│ execute(params, ctx) → Result # run the tool │ -│ deactivate() → void # cleanup │ -│ │ -│ execution_context: │ -│ sandbox : Sandbox # sandboxed env │ -│ plan : Plan # owning plan │ -│ changes : list[Change] # tracked mutations │ -│ resources: dict[str, BoundResource] # bound resources │ -│ │ -└──────────────────────────────────────────────────────────┘ +```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 ``` Every tool implements the same four lifecycle methods. The **tool adapter layer** is responsible for translating source-specific behavior into these methods. @@ -6144,45 +6108,32 @@ 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: -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Tool Adapter Layer │ -│ │ -│ ┌──────────────────┐ ┌───────────────────┐ ┌──────────────┐ │ -│ │ MCPToolAdapter │ │ AgentSkillAdapter │ │BuiltinAdapter│ │ -│ │ │ │ │ │ │ │ -│ │ discover(): │ │ discover(): │ │ discover(): │ │ -│ │ tools/list RPC │ │ parse SKILL.md │ │ return │ │ -│ │ → descriptors │ │ frontmatter │ │ hardcoded │ │ -│ │ │ │ → descriptor │ │ descriptors │ │ -│ │ activate(): │ │ │ │ │ │ -│ │ spawn server │ │ activate(): │ │ activate(): │ │ -│ │ process, init │ │ load full │ │ no-op │ │ -│ │ JSON-RPC conn │ │ SKILL.md body │ │ │ │ -│ │ │ │ into agent │ │ execute(): │ │ -│ │ execute(): │ │ context │ │ call native │ │ -│ │ tools/call RPC │ │ │ │ Python impl │ │ -│ │ → result │ │ execute(): │ │ │ │ -│ │ │ │ agent follows │ │ deactivate():│ │ -│ │ deactivate(): │ │ instructions, │ │ no-op │ │ -│ │ shutdown server │ │ runs scripts │ │ │ │ -│ └──────────────────┘ │ │ └──────────────┘ │ -│ │ deactivate(): │ │ -│ │ remove from ctx │ │ -│ └───────────────────┘ │ -│ │ │ │ │ -│ └────────────────────┼────────────────────┘ │ -│ │ │ -│ ┌───────────┴───────────┐ │ -│ │ Uniform Tool │ │ -│ │ Interface │ │ -│ │ │ │ -│ │ discover() → meta │ │ -│ │ activate() → void │ │ -│ │ execute(p, ctx) → R │ │ -│ │ deactivate() → void │ │ -│ └────────────────────────┘ │ -└──────────────────────────────────────────────────────────────────┘ +```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"] ``` ##### MCPToolAdapter @@ -6438,28 +6389,25 @@ This approach means: CleverAgents manages MCP server processes as part of the tool/skill/actor lifecycle: -``` -Tool or skill registration (agents tool add / agents skill add) - └─→ For each MCP server in tool or skill config: - ├─→ Validate server command/endpoint is reachable - └─→ Store server config in tool/skill record +```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"] -Actor activation (references skills or tool nodes) - └─→ For each MCP server in referenced tools/skills: - ├─→ Spawn server process (stdio) or connect (HTTP) - ├─→ MCP initialize handshake (capability negotiation) - ├─→ tools/list → register tools in actor's tool set - └─→ Subscribe to notifications/tools/list_changed + 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"] -Actor graph execution - └─→ LLM agent generates tool call - └─→ Tool Router → MCPToolAdapter.execute() - └─→ tools/call JSON-RPC → MCP server - └─→ Result → Change tracking → Return + 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"] -Actor deactivation - └─→ For each MCP server: - └─→ Clean shutdown of server process / connection + D["Actor deactivation"] --> D1["For each MCP server"] + D1 --> D2["Clean shutdown of server process / connection"] ``` ##### Sandbox Path Rewriting for MCP Tools @@ -6547,34 +6495,25 @@ A skill is **not** a single tool. It is a **container** that references one or m #### The Skill / Tool Distinction -``` -┌─────────────────────────────────────────────────────────────┐ -│ Skill: local/devops-toolkit │ -│ (a named, reusable collection) │ -│ │ -│ ┌───────────────────────────────────────────────────────┐ │ -│ │ Tools (from various sources): │ │ -│ │ │ │ -│ │ ● read_file() [builtin: file_operations] │ │ -│ │ ● write_file() [builtin: file_operations] │ │ -│ │ ● edit_file() [builtin: file_operations] │ │ -│ │ ● git_status() [builtin: git_operations] │ │ -│ │ ● git_diff() [builtin: git_operations] │ │ -│ │ ● create_issue() [mcp: github-server] │ │ -│ │ ● create_pr() [mcp: github-server] │ │ -│ │ ● list_repos() [mcp: github-server] │ │ -│ │ ● run_migrations() [custom] │ │ -│ │ │ │ -│ └───────────────────────────────────────────────────────┘ │ -│ │ -│ ┌───────────────────────────────────────────────────────┐ │ -│ │ Included Skills (hierarchical): │ │ -│ │ │ │ -│ │ ● local/pdf-processing → adds pdf tools │ │ -│ │ ● local/data-analysis → adds analysis tools │ │ -│ │ │ │ -│ └───────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ +```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 ``` **Tools** are independently registered, atomic units of execution (see the **Tools** section above for full details). Each tool has: @@ -6858,51 +6797,33 @@ 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. -``` -┌──────────────────────────────────────────────────────────┐ -│ Skill Registry │ -│ │ -│ ┌────────────────────────────────────────────────────┐ │ -│ │ Skill Index │ │ -│ │ │ │ -│ │ skill_name → SkillRecord { │ │ -│ │ name, description, tags, config_path, │ │ -│ │ includes: [skill_name, ...], │ │ -│ │ tool_refs: [tool_name, ...], (from Tool Reg.) │ │ -│ │ anonymous_tools: [ToolDef, ...], │ │ -│ │ flattened_tools: [ToolDescriptor, ...], │ │ -│ │ overrides: { tool_name → metadata_patch }, │ │ -│ │ capability_summary │ │ -│ │ } │ │ -│ └────────────────────────────────────────────────────┘ │ -│ │ -│ Operations: │ -│ 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 │ -│ "Does this plan's required skills exist? │ -│ Are they checkpointable? Do permissions align?" │ -│ refresh(name) → void │ -│ "Re-discover after MCP list_changed notification" │ -│ │ -│ Populated by: │ -│ • agents skill add [--upgrade] CLI command │ -│ • Dynamic refresh on MCP notifications │ -│ │ -│ Depends on: │ -│ • Tool Registry (resolve named tool references) │ -│ │ -│ Consumed by: │ -│ • Actor activation (resolve skill references) │ -│ • Plan validation ("requires checkpointable writes") │ -│ • Permission enforcement ("deny shell_execute") │ -│ • Agent context injection ("available tools: ...") │ -│ │ -└──────────────────────────────────────────────────────────┘ +```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" ``` Both registries persist in the database (local SQLite or server). MCP server tools are refreshed dynamically when `notifications/tools/list_changed` events are received. @@ -7141,141 +7062,68 @@ 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: -``` - GIT-CHECKOUT STANDALONE STANDALONE - (composition) FS-DIRECTORY FS-MOUNT - ───────────── ──────────── ──────── +```kroki-mermaid +flowchart TD + subgraph GC["GIT-CHECKOUT (composition)"] + gc["git-checkout\n/home/user/projects/myapp"] + end - ┌─────────────────────┐ - │ git-checkout │ - │ /home/user/projects/ │ - │ myapp │ - └─────┬────────┬───────┘ - │ │ - ↓ ↓ - ┌───────────┐ ┌──────────────────────┐ ┌───────────────┐ ┌───────────────┐ - │ git │ │ fs-directory │ │ fs-directory │ │ fs-mount │ - │ (repo │ │ (worktree root: │ │ (/opt/deploy/ │ │ (/mnt/data) │ - │ object DB)│ │ /home/user/projects/ │ │ myapp) │ └───────┬───────┘ - └─┬─┬──┬────┘ │ myapp) │ └───┬───────┬───┘ │ - │ │ │ └─┬──────────┬────────┘ │ │ ┌────┴────────┐ - │ │ │ │ │ │ │ │ fs-directory│ - │ │ │ ↓ ↓ ↓ ↓ │ (root: /) │ - │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ └────────────┘ - │ │ │ │ fs-dir │ │ fs-file │ │ fs-dir │ │ fs-file │ - │ │ │ │ (src/) │ │ README │ │ (src/) │ │ README │ - │ │ │ └──┬────┬──┘ │ .md │ └──┬────┬──┘ │ .md │ - │ │ │ │ │ └─────────┘ │ │ └─────────┘ - │ │ │ ↓ ↓ ↓ ↓ - │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐ - │ │ │ │ fs-file│ │ fs-file│ │ fs-file│ │ fs-file│ │fs-symlink│ - │ │ │ │ app.ts │ │ main.ts│ │ app.ts │ │ main.ts│ │ link.txt │ - │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ └──────────┘ - │ │ │ - │ │ ↓ - │ │ ┌──────────────┐ - │ │ │ git-submodule │ - │ │ │ (lib/shared) │ - │ │ └──────────────┘ - │ │ - │ ↓ - │ ┌──────────┐ ┌──────────┐ - │ │git-remote│ │ git-tag │ - │ │(origin) │ │ (v1.0.0) │ - │ └──────────┘ └──────────┘ - │ - ↓ - ┌──────────┐ ┌──────────┐ - │git-branch│ │git-stash │ - │(main) │ │(stash@{0})│ - └────┬─────┘ └──────────┘ - │ - ↓ - ┌──────────┐ - │git-commit│ - │(a1b2c3d) │ - └────┬─────┘ - │ - ↓ - ┌──────────┐ - │ git-tree │ ← root tree object - │ (e8f1...)│ - └──┬────┬──┘ - │ │ - ↓ ↓ - ┌────────────┐ ┌──────────┐ - │git-tree- │ │ git-tree │ ← subtree for src/ - │entry │ │ (4c2d...)│ - │ README.md │ └──┬────┬──┘ - └────────────┘ │ │ - ↓ ↓ - ┌────────────┐ ┌────────────┐ - │git-tree- │ │git-tree- │ - │entry │ │entry │ - │src/app.ts │ │src/main.ts │ - └────────────┘ └────────────┘ + gc --> git["git\n(repo object DB)"] + gc --> fsroot["fs-directory\n(worktree root)"] + subgraph SFS["STANDALONE FS-DIRECTORY"] + sfs["fs-directory\n(/opt/deploy/myapp)"] + end - ╔═══════════════════════════════════════════════════════════════╗ - ║ VIRTUAL LAYER ║ - ║ (abstract identities linking equivalent physical resources) ║ - ║ ║ - ║ ┌───────────────────────────────────────────────────────┐ ║ - ║ │ file (app.ts @ sha256:9f8e...) │ ║ - ║ │ Same content + name + permissions │ ║ - ║ │ │ ║ - ║ │ children: │ ║ - ║ │ ├─ fs-file (worktree: src/app.ts) [physical] │ ║ - ║ │ ├─ fs-file (deploy: src/app.ts) [physical] │ ║ - ║ │ └─ git-tree-entry (main:a1b...:app.ts) [physical] │ ║ - ║ └───────────────────────────────────────────────────────┘ ║ - ║ ║ - ║ ┌───────────────────────────────────────────────────────┐ ║ - ║ │ directory (src/ @ merkle:3d4f...) │ ║ - ║ │ Same recursive directory content │ ║ - ║ │ │ ║ - ║ │ children: │ ║ - ║ │ ├─ fs-directory (worktree: src/) [physical] │ ║ - ║ │ ├─ fs-directory (deploy: src/) [physical] │ ║ - ║ │ └─ git-tree (main:a1b...:4c2d...) [physical] │ ║ - ║ └───────────────────────────────────────────────────────┘ ║ - ║ ║ - ║ ┌───────────────────────────────────────────────────────┐ ║ - ║ │ commit (a1b2c3d) │ ║ - ║ │ Same commit hash across repos │ ║ - ║ │ │ ║ - ║ │ children: │ ║ - ║ │ ├─ git-commit (local/app:main:a1b2c3d) [physical] │ ║ - ║ │ └─ git-commit (upstream:main:a1b2c3d) [physical] │ ║ - ║ └───────────────────────────────────────────────────────┘ ║ - ║ ║ - ║ ┌───────────────────────────────────────────────────────┐ ║ - ║ │ branch (main @ a1b2c3d) │ ║ - ║ │ Same branch name + HEAD across repos │ ║ - ║ │ │ ║ - ║ │ children: │ ║ - ║ │ ├─ git-branch (local/app:main) [physical] │ ║ - ║ │ └─ git-branch (upstream:main) [physical] │ ║ - ║ └───────────────────────────────────────────────────────┘ ║ - ║ ║ - ║ ┌───────────────────────────────────────────────────────┐ ║ - ║ │ remote (https://github.com/org/repo) │ ║ - ║ │ Same remote URL across repos │ ║ - ║ │ │ ║ - ║ │ children: │ ║ - ║ │ ├─ git-remote (local/app:origin) [physical] │ ║ - ║ │ └─ git-remote (upstream:origin) [physical] │ ║ - ║ └───────────────────────────────────────────────────────┘ ║ - ║ ║ - ║ ┌───────────────────────────────────────────────────────┐ ║ - ║ │ tree (e8f1...9d2a) │ ║ - ║ │ Same tree hash across commits/repos │ ║ - ║ │ │ ║ - ║ │ children: │ ║ - ║ │ ├─ git-tree (local/app:main:e8f1...) [physical] │ ║ - ║ │ └─ git-tree (upstream:main:e8f1...) [physical] │ ║ - ║ └───────────────────────────────────────────────────────┘ ║ - ╚═══════════════════════════════════════════════════════════════╝ + subgraph SM["STANDALONE FS-MOUNT"] + mount["fs-mount\n(/mnt/data)"] + end + + mount --> mountroot["fs-directory\n(root: /)"] + + 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)"] + + 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"] + + fsroot --> srcdir["fs-directory\n(src/)"] + fsroot --> readme["fs-file\nREADME.md"] + srcdir --> app["fs-file\napp.ts"] + srcdir --> main["fs-file\nmain.ts"] + + 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"] + + 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 + + app -.-> vfile + sapp -.-> vfile + entry2 -.-> vfile + srcdir -.-> vdir + ssrcdir -.-> vdir + subtree -.-> vdir + commit -.-> vcommit + branch -.-> vbranch + remote -.-> vremote + tree -.-> vtree ``` **Reading the diagram:** @@ -7329,77 +7177,60 @@ 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): -``` -local/acme-app [git-checkout] physical -│ -├── local/acme-app:repo [git] physical -│ │ (repo object database at /home/alice/projects/acme-dashboard/.git) -│ │ -│ ├── local/acme-app:repo:origin [git-remote] physical -│ │ git@github.com:acmecorp/dashboard.git -│ │ -│ ├── local/acme-app:repo:v1.0.0 [git-tag] physical -│ │ -│ ├── local/acme-app:repo:lib/shared [git-submodule] physical -│ │ git@github.com:acmecorp/shared-lib.git @ c4d5e6f -│ │ -│ ├── local/acme-app:repo:stash@{0} [git-stash] physical -│ │ -│ ├── local/acme-app:repo:main [git-branch] physical -│ │ └── local/acme-app:repo:main:a7f3e21 [git-commit] physical -│ │ └── ...repo:main:a7f3e21:tree [git-tree] physical ← root tree -│ │ ├── ...repo:main:a7f3e21:README.md [git-tree-entry] physical -│ │ ├── ...repo:main:a7f3e21:package.json [git-tree-entry] physical -│ │ └── ...repo:main:a7f3e21:src/ [git-tree] physical ← subtree -│ │ ├── ...a7f3e21:src/app.ts [git-tree-entry] physical -│ │ ├── ...a7f3e21:src/api.ts [git-tree-entry] physical -│ │ └── ...a7f3e21:src/utils.ts [git-tree-entry] physical -│ │ -│ └── local/acme-app:repo:develop [git-branch] physical -│ └── local/acme-app:repo:develop:b2c4d8e [git-commit] physical -│ └── ...repo:develop:b2c4d8e:tree [git-tree] physical -│ ├── ...b2c4d8e:src/ [git-tree] physical -│ │ ├── ...b2c4d8e:src/app.ts [git-tree-entry] physical -│ │ ├── ...b2c4d8e:src/api.ts [git-tree-entry] physical (modified) -│ │ └── ... -│ └── ... -│ -└── local/acme-app:worktree [fs-directory] physical - │ (worktree root: /home/alice/projects/acme-dashboard/) - │ - ├── local/acme-app:worktree:src/ [fs-directory] physical - │ ├── ...worktree:src/app.ts [fs-file] physical - │ ├── ...worktree:src/api.ts [fs-file] physical - │ └── ...worktree:src/utils.ts [fs-file] physical - ├── local/acme-app:worktree:package.json [fs-file] physical - ├── local/acme-app:worktree:README.md [fs-file] physical - └── local/acme-app:worktree:docs → ../docs [fs-symlink] physical +```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"] ``` 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: -``` -local/acme-upstream [git] physical -│ (accessed via git@github.com:acmecorp/dashboard.git — exists on GitHub's servers) -│ -├── local/acme-upstream:origin [git-remote] physical -│ git@github.com:acmecorp/dashboard.git -│ -├── local/acme-upstream:v1.0.0 [git-tag] physical -│ -├── local/acme-upstream:main [git-branch] physical -│ └── local/acme-upstream:main:a7f3e21 [git-commit] physical -│ └── ...main:a7f3e21:tree [git-tree] physical -│ ├── ...a7f3e21:src/ [git-tree] physical -│ │ ├── ...a7f3e21:src/app.ts [git-tree-entry] physical -│ │ ├── ...a7f3e21:src/api.ts [git-tree-entry] physical -│ │ └── ... -│ └── ... -│ -└── local/acme-upstream:develop [git-branch] physical - └── ... +```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)"] ``` 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. @@ -7408,16 +7239,15 @@ This is the key difference from `git-checkout`: a `git` resource represents a sp **`local/acme-deploy`** (type: `fs-directory`, standalone) discovers: -``` -local/acme-deploy [fs-directory] physical -│ (directory: /opt/deploy/acme-dashboard/) -│ -├── local/acme-deploy:src/ [fs-directory] physical -│ ├── local/acme-deploy:src/app.ts [fs-file] physical -│ ├── local/acme-deploy:src/api.ts [fs-file] physical -│ └── local/acme-deploy:src/utils.ts [fs-file] physical -├── local/acme-deploy:package.json [fs-file] physical -└── local/acme-deploy:README.md [fs-file] physical +```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"] ``` 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. @@ -7426,66 +7256,58 @@ 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: -``` -VIRTUAL LAYER (auto-created by equivalence matching) -──────────────────────────────────────────────────── +```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 -directory: src/ (merkle:3d4f...a2b1) [virtual] -│ (same recursive directory content — links fs-directory + git-tree) -├── ← local/acme-app:worktree:src/ [fs-directory] (same content) -├── ← local/acme-deploy:src/ [fs-directory] (same content) -├── ← local/acme-app:repo:main:a7f3e21:src/ [git-tree] (same content) -└── ← local/acme-upstream:main:a7f3e21:src/ [git-tree] (same content) + 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 -file: app.ts (sha256:9f8e...c4d5) [virtual] -│ (same content + filename + permissions — links fs-file + git-tree-entry) -├── ← local/acme-app:worktree:src/app.ts [fs-file] (same content) -├── ← local/acme-deploy:src/app.ts [fs-file] (same content) -├── ← local/acme-app:repo:main:a7f3e21:src/app.ts [git-tree-entry] (same content) -└── ← local/acme-upstream:main:a7f3e21:src/app.ts [git-tree-entry] (same content) + 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 -file: utils.ts (sha256:a2b1...7f4c) [virtual] -├── ← local/acme-app:worktree:src/utils.ts [fs-file] -├── ← local/acme-deploy:src/utils.ts [fs-file] -├── ← local/acme-app:repo:main:a7f3e21:src/utils.ts [git-tree-entry] -└── ← local/acme-upstream:main:a7f3e21:src/utils.ts [git-tree-entry] + 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 -file: api.ts (sha256:e1d3...8a9b) [virtual] -├── ← local/acme-app:worktree:src/api.ts [fs-file] -├── ← local/acme-deploy:src/api.ts [fs-file] -├── ← local/acme-app:repo:main:a7f3e21:src/api.ts [git-tree-entry] -├── ← local/acme-upstream:main:a7f3e21:src/api.ts [git-tree-entry] -└── (NOT linked: ...develop:b2c4d8e:src/api.ts — different content on develop branch) + 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)"] -commit: a7f3e21 [virtual] -│ (same commit hash across repos — shared history) -├── ← local/acme-app:repo:main:a7f3e21 [git-commit] -└── ← local/acme-upstream:main:a7f3e21 [git-commit] + acmeCommit["acme-app:repo:main:a7f3e21 (git-commit)"] -.-> vCommit + upstreamCommit["acme-upstream:main:a7f3e21 (git-commit)"] -.-> vCommit -branch: main @ a7f3e21 [virtual] -│ (same branch name + HEAD across repos) -├── ← local/acme-app:repo:main [git-branch] -└── ← local/acme-upstream:main [git-branch] + acmeBranch["acme-app:repo:main (git-branch)"] -.-> vBranch + upstreamBranch["acme-upstream:main (git-branch)"] -.-> vBranch -tag: v1.0.0 → a7f3e21 [virtual] -│ (same tag name + target across repos) -├── ← local/acme-app:repo:v1.0.0 [git-tag] -└── ← local/acme-upstream:v1.0.0 [git-tag] + acmeTag["acme-app:repo:v1.0.0 (git-tag)"] -.-> vTag + upstreamTag["acme-upstream:v1.0.0 (git-tag)"] -.-> vTag -remote: git@github.com:acmecorp/dashboard.git [virtual] -│ (same remote URL across repos) -├── ← local/acme-app:repo:origin [git-remote] -└── ← local/acme-upstream:origin [git-remote] + acmeRemote["acme-app:repo:origin (git-remote)"] -.-> vRemote + upstreamRemote["acme-upstream:origin (git-remote)"] -.-> vRemote -tree: e8f1...9d2a [virtual] -│ (same tree hash across repos/commits) -├── ← local/acme-app:repo:main:a7f3e21:tree [git-tree] -└── ← local/acme-upstream:main:a7f3e21:tree [git-tree] + acmeTree["acme-app:repo:main:a7f3e21:tree (git-tree)"] -.-> vTree + upstreamTree["acme-upstream:main:a7f3e21:tree (git-tree)"] -.-> vTree -submodule: lib/shared @ git@github.com:acmecorp/shared-lib.git [virtual] -│ (same submodule URL + path across repos) -├── ← local/acme-app:repo:lib/shared [git-submodule] -└── ← (would link to any other repo with the same submodule) + acmeSubmod["acme-app:repo:lib/shared (git-submodule)"] -.-> vSubmod ``` **How the `directory` virtual type works:** @@ -7583,100 +7405,81 @@ 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: -``` - GIT-CHECKOUT GIT (remote, no checkout) STANDALONE FS-DIRECTORY - ──────────── ──────────────────────── ─────────────────────── +```kroki-mermaid +flowchart TD + subgraph GCO["GIT-CHECKOUT"] + gco["git-checkout\nlocal/app"] + end - ┌──────────────┐ - │ git-checkout │ - │ local/app │ - └───┬─────┬────┘ - │ │ - ↓ └──────────────────────────────────────────────┐ - ↓ - ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ - │ git │ │ git │ │ fs-directory │ │ fs-directory │ - │ local/app: │ │ local/ │ │ local/app: │ │ local/deploy │ - │ repo │ │ upstream │ │ worktree │ │ (/opt/deploy/ │ - └──┬──┬──┬─────┘ └──┬──┬──┬─────┘ │ (/home/user/ │ │ myapp) │ - │ │ │ │ │ │ │ projects/myapp) │ └───┬──────┬───────┘ - │ │ ↓ │ │ ↓ └───┬──────┬───────┘ │ │ - │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │ │ - │ │ │git-remote│ │ │ │git-remote│ ↓ ↓ ↓ ↓ - │ │ │(origin) │ │ │ │(origin) │┌────────┐┌────────┐ ┌────────┐┌────────┐ - │ │ └──────────┘ │ │ └──────────┘│fs-dir ││fs-file │ │fs-dir ││fs-file │ - │ │ │ │ │(src/) ││(README │ │(src/) ││(README │ - │ ↓ │ ↓ └──┬──┬──┘│.md) │ └──┬──┬──┘│.md) │ - │ ┌──────────┐ │ ┌──────────┐ │ │ └────────┘ │ │ └────────┘ - │ │ git-tag │ │ │ git-tag │ ↓ ↓ ↓ ↓ - │ │ (v1.0.0) │ │ │ (v1.0.0) │ ┌──────┐┌────────┐ ┌──────┐┌────────┐ - │ └──────────┘ │ └──────────┘ │fs- ││fs-file │ │fs- ││fs-file │ - ↓ ↓ │file ││(main │ │file ││(main │ - ┌──────────┐ ┌──────────┐ │(app ││.ts) │ │(app ││.ts) │ - │git-branch│ │git-branch│ │.ts) │└────────┘ │.ts) │└────────┘ - │(main) │ │(main) │ └──────┘ └──────┘ - └────┬─────┘ └────┬─────┘ - ↓ ↓ - ┌──────────┐ ┌──────────┐ - │git-commit│ │git-commit│ - │(a1b2c3d) │ │(a1b2c3d) │ - └────┬─────┘ └────┬─────┘ - ↓ ↓ - ┌──────────┐ ┌──────────┐ - │ git-tree │ │ git-tree │ ← root tree objects - │ (e8f1...)│ │ (e8f1...)│ - └──┬────┬──┘ └──┬────┬──┘ - │ │ │ │ - ↓ ↓ ↓ ↓ - ┌────────────┐ ┌────────┐┌────────────┐ ┌────────┐ - │git-tree- │ │git-tree││git-tree- │ │git-tree│ - │entry │ │(src/) ││entry │ │(src/) │ - │(README.md) │ └──┬──┬──┘│(README.md) │ └──┬──┬──┘ - └────────────┘ │ │ └────────────┘ │ │ - ↓ ↓ ↓ ↓ - ┌────────────┐ ┌────────┐ ┌────────────┐ ┌────────┐ - │git-tree- │ │git-tree│ │git-tree- │ │git-tree│ - │entry │ │-entry │ │entry │ │-entry │ - │(src/app.ts)│ │(src/ │ │(src/app.ts)│ │(src/ │ - └────────────┘ │main.ts)│ └────────────┘ │main.ts)│ - └────────┘ └────────┘ + subgraph GR["GIT (remote)"] + gr["git\nlocal/upstream"] + end - VIRTUAL LAYER (links equivalent physical resources) - ─────────────────────────────────────────────────── + subgraph SFD["STANDALONE FS-DIRECTORY"] + sfd["fs-directory\nlocal/deploy\n(/opt/deploy/myapp)"] + end - ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ - │ file │ │ directory │ │ commit │ │ branch │ - │ (app.ts@v1) │ │ (src/@v1) │ │ (a1b2c3d) │ │ (main@a1b...) │ - │ │ │ │ │ │ │ │ - │ children: │ │ children: │ │ children: │ │ children: │ - │ ├ fs-file │ │ ├ fs-directory │ │ ├ git-commit │ │ ├ git-branch │ - │ │ (wt:app.ts) │ │ │ (wt:src/) │ │ │ (local) │ │ │ (local) │ - │ ├ fs-file │ │ ├ fs-directory │ │ └ git-commit │ │ └ git-branch │ - │ │ (dpl:app.ts)│ │ │ (dpl:src/) │ │ (upstream) │ │ (upstream) │ - │ ├ git-tree- │ │ ├ git-tree │ └────────────────┘ └────────────────┘ - │ │ entry(local)│ │ │ (local:src/) │ - │ └ git-tree- │ │ └ git-tree │ ┌────────────────┐ ┌────────────────┐ - │ entry(upstr)│ │ (upstr:src/) │ │ tag │ │ remote │ - └────────────────┘ └────────────────┘ │ (v1.0.0) │ │ (github.com/ │ - │ │ │ org/repo) │ - │ children: │ │ │ - │ ├ git-tag │ │ children: │ - │ │ (local) │ │ ├ git-remote │ - │ └ git-tag │ │ │ (local) │ - │ (upstream) │ │ └ git-remote │ - └────────────────┘ │ (upstream) │ - └────────────────┘ + gco --> gcoGit["git\nlocal/app:repo"] + gco --> gcoFs["fs-directory\nlocal/app:worktree"] - ┌────────────────┐ - │ tree │ - │ (e8f1...9d2a) │ - │ │ - │ children: │ - │ ├ git-tree │ - │ │ (local) │ - │ └ git-tree │ - │ (upstream) │ - └────────────────┘ + 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)"] + + 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)"] + + 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)"] + + 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)"] + + 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 ``` **Key properties of the DAG:** @@ -7801,63 +7604,53 @@ 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: -``` -┌───────────────────────────────────────────────────────────────┐ -│ Resource Registry │ -│ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Resource Index │ │ -│ │ │ │ -│ │ resource_name → ResourceRecord { │ │ -│ │ name, type, physical_or_virtual, │ │ -│ │ properties (type-specific values), │ │ -│ │ capabilities (readable, writable, sandboxable, ...), │ │ -│ │ parents: [(resource_name, auto: bool), ...], │ │ -│ │ children: [(resource_name, auto: bool), ...], │ │ -│ │ content_hash (for identity tracking), │ │ -│ │ linked_projects: [project_name, ...], │ │ -│ │ created_at, updated_at │ │ -│ │ } │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Resource Type Index │ │ -│ │ │ │ -│ │ type_name → ResourceTypeRecord { │ │ -│ │ name, description, source (built-in | custom), │ │ -│ │ physical_or_virtual, user_addable, │ │ -│ │ cli_arguments, allowed_parent_types, │ │ -│ │ child_types (auto/manual), sandbox_strategy, │ │ -│ │ handler, capabilities, config_path │ │ -│ │ } │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ -│ Operations: │ -│ add(type, name, properties) → ResourceRecord │ -│ upgrade(name, properties) → ResourceRecord │ -│ remove(name) → void (cascades to auto children) │ -│ lookup(name) → ResourceRecord │ -│ list(filters) → ResourceRecord[] │ -│ tree(name, depth) → DAG subtree │ -│ link_child(parent, child) → void │ -│ unlink_child(parent, child) → void │ -│ refresh(name) → void (re-run auto-discovery) │ -│ find_by_content(hash) → ResourceRecord[] │ -│ find_virtual_parent(resource) → ResourceRecord | None │ -│ │ -│ Populated by: │ -│ • agents resource add CLI command │ -│ • Auto-discovery during registration and refresh │ -│ • Content-identity linking │ -│ │ -│ Consumed by: │ -│ • Tool binding resolution (match slots to resources) │ -│ • Sandbox creation (lazy, on first access) │ -│ • Change tracking (route changes to correct resource) │ -│ • Project linking (agents project link-resource) │ -│ • Plan validation ("does this resource exist?") │ -│ │ -└───────────────────────────────────────────────────────────────┘ +```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" ``` The Resource Registry persists in the database (local SQLite or server). It works alongside the Tool Registry and Skill Registry. @@ -7989,26 +7782,11 @@ 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: -``` -Tool invocation: edit_file(path="src/main.py", ...) - ↓ -┌─────────────────────────────────────────────┐ -│ Resource Router │ -│ 1. Check tool's resource bindings │ -│ → tool has slot "repo" bound to │ -│ local/api-repo (type: git-checkout) │ -│ 2. Resolve path within bound resource │ -│ → local/api-repo:worktree:src/main.py │ -│ 3. Route to resource handler │ -│ → GitCheckoutHandler │ -└─────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────┐ -│ GitCheckoutHandler │ -│ - Resolves path to sandbox worktree │ -│ - Operates on sandboxed state │ -│ - Returns Change record │ -└─────────────────────────────────────────────┘ +```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"] ``` When a tool has multiple resource slots bound, the path scheme or slot name disambiguates: diff --git a/mkdocs.yml b/mkdocs.yml index 043344a79..752d70d55 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -34,6 +34,10 @@ extra_javascript: plugins: - search + - kroki: + server_url: !ENV [KROKI_SERVER_URL, 'https://kroki.io'] + enable_mermaid: true + fence_prefix: kroki- markdown_extensions: - admonition diff --git a/pyproject.toml b/pyproject.toml index 97b2d0330..916b69a99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,7 @@ docs = [ "mkdocs>=1.6.1", "mkdocs-material>=9.6.0", "mkdocstrings[python]>=0.24.0", + "mkdocs-kroki-plugin>=1.2.0", ] [project.urls]