Chapter 33

Skill Version Management and Dependency Resolution

Chapter 33: Skill Version Management and Dependency Resolution

As Agent systems grow, the number of Skills can quickly balloon to dozens or even hundreds. When multiple Skills share dependencies, when the same library exists in multiple versions, or when team members use different Skill versionsโ€”version chaos becomes the greatest threat to system stability. This chapter systematically covers Hermes Agent's Skill version management, from version numbering conventions to dependency resolution algorithms, from conflict resolution to lock file mechanisms.


33.1 Skill Version Numbering: Semantic Versioning (SemVer)

Hermes Skills use Semantic Versioning 2.0.0 (SemVer), with the format:

MAJOR.MINOR.PATCH[-pre-release][+build-metadata]

The Three Version Segments

Segment Meaning When to Increment Example
MAJOR Breaking changes API incompatibility 2.0.0
MINOR New features Backward-compatible additions 1.3.0
PATCH Bug fixes Backward-compatible fixes 1.3.5

Pre-release Identifiers

1.0.0-alpha        # Internal testing
1.0.0-alpha.1      # Internal testing, round 1
1.0.0-beta.2       # Public beta, round 2
1.0.0-rc.1         # Release candidate 1
1.0.0              # Stable release

Version Declaration in skill.yaml

Every Skill package declares its version in skill.yaml at the package root:

# skill.yaml
name: web-search-skill
version: "2.1.3"
description: "Fetch the latest web information via multi-engine concurrent search"
author: "NousResearch Labs"
license: "MIT"
homepage: "https://clawhub.ai/skills/web-search"

# Minimum Hermes runtime version
hermes_runtime: ">=1.5.0,<3.0.0"

# Python version requirement
python: ">=3.10"

# Skill dependencies
dependencies:
  core-http-skill: "^1.2.0"
  json-parser-skill: "~2.0.0"
  rate-limiter-skill: ">=1.0.0,<2.0.0"

# Optional dependencies
optional_dependencies:
  browser-render-skill: "^1.0.0"  # For JavaScript-rendered pages

# Development dependencies (not shipped with the package)
dev_dependencies:
  skill-test-kit: "^3.0.0"
  mock-mcp-server: "^1.1.0"

Version Range Specifiers

dependencies:
  skill-a: "1.2.3"       # Exact version
  skill-b: "^1.2.3"      # Compatible: >=1.2.3,<2.0.0
  skill-c: "~1.2.3"      # Approximate: >=1.2.3,<1.3.0
  skill-d: ">=1.0,<2.0"  # Range
  skill-e: "*"            # Any version (not recommended)
  skill-f: "latest"       # Latest stable (avoid in production)

33.2 Dependency Declaration Format and Resolution Flow

Full Dependency Declaration Structure

dependencies:
  web-scraper-skill:
    version: "^2.0.0"
    source: "clawhub"
    optional: false
    extras:
      - "proxy-support"
      - "cache-layer"
    
  private-nlp-skill:
    version: "1.5.0"
    source: "git"
    git_url: "https://github.com/myorg/nlp-skill.git"
    git_ref: "v1.5.0"
    
  local-util-skill:
    version: "*"
    source: "local"
    path: "../skills/local-util"

Resolution Pipeline

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚               Dependency Resolver               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                 โ”‚
โ”‚  1. Read root skill.yaml                        โ”‚
โ”‚         โ†“                                      โ”‚
โ”‚  2. Build initial dependency list               โ”‚
โ”‚         โ†“                                      โ”‚
โ”‚  3. Query registry for available versions       โ”‚
โ”‚         โ†“                                      โ”‚
โ”‚  4. Recursively resolve transitive deps         โ”‚
โ”‚         โ†“                                      โ”‚
โ”‚  5. Detect version conflicts โ†’ apply strategy   โ”‚
โ”‚         โ†“                                      โ”‚
โ”‚  6. Detect cyclic dependencies โ†’ raise error    โ”‚
โ”‚         โ†“                                      โ”‚
โ”‚  7. Generate deterministic dependency tree      โ”‚
โ”‚         โ†“                                      โ”‚
โ”‚  8. Write skill.lock file                       โ”‚
โ”‚                                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Python Implementation: Building the Dependency Graph

# hermes/skill/dependency_graph.py
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set
from packaging.version import Version
from packaging.specifiers import SpecifierSet

@dataclass
class SkillNode:
    """A node in the dependency graph"""
    name: str
    version: Version
    dependencies: Dict[str, SpecifierSet] = field(default_factory=dict)
    resolved_deps: Dict[str, 'SkillNode'] = field(default_factory=dict)
    
    def __hash__(self):
        return hash((self.name, str(self.version)))


class DependencyGraph:
    """Directed acyclic graph of Skill dependencies"""
    
    def __init__(self):
        self.nodes: Dict[str, SkillNode] = {}
        self.edges: Dict[str, Set[str]] = {}
    
    def add_node(self, node: SkillNode):
        self.nodes[node.name] = node
        self.edges[node.name] = set(node.dependencies.keys())
    
    def topological_sort(self) -> List[str]:
        """Topological sort to determine installation order"""
        visited = set()
        result = []
        
        def dfs(name: str, path: Set[str]):
            if name in path:
                cycle = " -> ".join(list(path)) + " -> " + name
                raise CyclicDependencyError(f"Cyclic dependency detected: {cycle}")
            if name in visited:
                return
            path.add(name)
            for dep in self.edges.get(name, set()):
                dfs(dep, path.copy())
            visited.add(name)
            result.append(name)
        
        for name in self.nodes:
            dfs(name, set())
        return result


class CyclicDependencyError(Exception):
    pass

33.3 Cyclic Dependency Detection

Cyclic dependencies are the most intractable problem in dependency management: Skill A depends on B, and B depends back on A, creating a deadlock.

Detection with Three-Color DFS

# hermes/skill/cycle_detector.py
from enum import Enum
from typing import Dict, List, Optional

class NodeState(Enum):
    UNVISITED    = "unvisited"
    IN_PROGRESS  = "in_progress"   # Currently on the DFS stack
    DONE         = "done"

class CycleDetector:
    """Detects cycles using three-color DFS marking"""
    
    def __init__(self, graph: Dict[str, List[str]]):
        self.graph = graph
        self.state = {n: NodeState.UNVISITED for n in graph}
        self.cycles: List[List[str]] = []
    
    def detect(self) -> List[List[str]]:
        for node in self.graph:
            if self.state[node] == NodeState.UNVISITED:
                self._dfs(node, [])
        return self.cycles
    
    def _dfs(self, node: str, path: List[str]):
        self.state[node] = NodeState.IN_PROGRESS
        path.append(node)
        
        for neighbor in self.graph.get(node, []):
            if neighbor not in self.state:
                continue
            if self.state[neighbor] == NodeState.IN_PROGRESS:
                cycle_start = path.index(neighbor)
                self.cycles.append(path[cycle_start:] + [neighbor])
            elif self.state[neighbor] == NodeState.UNVISITED:
                self._dfs(neighbor, path.copy())
        
        self.state[node] = NodeState.DONE
    
    def format_report(self) -> str:
        if not self.cycles:
            return "No cyclic dependencies detected."
        lines = [f"Found {len(self.cycles)} cyclic dependency(ies):"]
        for i, cycle in enumerate(self.cycles, 1):
            lines.append(f"  Cycle {i}: {' -> '.join(cycle)}")
        return "\n".join(lines)

33.4 Version Conflict Resolution Strategies

Typical Conflict Scenario

Root project
โ”œโ”€โ”€ [email protected]
โ”‚   โ””โ”€โ”€ requires: skill-util@^1.2.0   (accepts 1.2.x โ€“ 1.9.x)
โ””โ”€โ”€ [email protected]
    โ””โ”€โ”€ requires: skill-util@^2.0.0   (accepts 2.0.x โ€“ 2.9.x)

^1.x and ^2.x are mutually incompatible.

Four Resolution Strategies

from enum import Enum

class ConflictStrategy(Enum):
    HIGHEST_VERSION = "highest"   # Pick the highest compatible version
    LOWEST_VERSION  = "lowest"    # Pick the lowest (most conservative)
    FAIL_FAST       = "fail"      # Raise an error immediately
    MULTI_VERSION   = "multi"     # Allow multiple versions (isolation)

Strategy Comparison

Strategy Best For Pros Cons
HIGHEST_VERSION Daily development (default) Latest bug fixes May introduce regressions
LOWEST_VERSION Conservative production Most stable Missing newer features
FAIL_FAST Strict CI pipelines Forces explicit decisions Requires manual resolution
MULTI_VERSION Large isolated projects Zero conflicts Double memory footprint

33.5 Upgrade and Rollback Operations

Upgrade Commands

# Upgrade a single Skill to latest compatible version
hermes skill upgrade web-search-skill

# Upgrade to a specific version
hermes skill upgrade [email protected]

# Dry-run to preview changes
hermes skill upgrade web-search-skill --dry-run

# Upgrade all Skills (use with caution)
hermes skill upgrade --all

Atomic Upgrade with Rollback

class SkillUpgrader:
    """Atomic upgrade manager with rollback support"""
    
    def upgrade(self, skill_name: str, target_version: str) -> bool:
        backup_path = self._backup_skill(skill_name)
        try:
            self._download_skill(skill_name, target_version)
            self._verify_skill(skill_name, target_version)
            self._run_migrations(skill_name, target_version)
            self._update_lockfile(skill_name, target_version)
            print(f"โœ“ {skill_name} upgraded to {target_version}")
            return True
        except Exception as e:
            print(f"โœ— Upgrade failed: {e}. Rolling back...")
            self._rollback_skill(skill_name, backup_path)
            return False

33.6 The Skill Lock File

The skill.lock file is the cornerstone of deterministic buildsโ€”it guarantees every developer, every CI run, and every deployment installs exactly the same dependency versions.

skill.lock Format

{
  "_meta": {
    "generated_at": "2024-11-15T09:23:41Z",
    "hermes_version": "1.7.2",
    "content_hash": "sha256:a3b8c9d2e4f1..."
  },
  "skills": {
    "web-search-skill": {
      "version": "2.1.3",
      "source": "clawhub",
      "resolved_url": "https://registry.clawhub.ai/packages/web-search-skill-2.1.3.tar.gz",
      "integrity": "sha512:ZmFrZWhhc2hv...",
      "dependencies": {
        "core-http-skill": "1.8.2",
        "json-parser-skill": "2.0.1"
      }
    }
  }
}

Lock File Best Practices

Rule 1: Always commit skill.lock to version control
  โ†’ Guarantees identical environments across the team

Rule 2: Never manually edit skill.lock
  โ†’ Always regenerate via `hermes skill install` or `hermes skill lock`

Rule 3: Use --frozen-lockfile in CI
  โ†’ Prevents unexpected dependency updates during CI runs

Rule 4: Review lock file changes in pull requests
  โ†’ Every dependency update should be a conscious decision

Rule 5: Run regular security audits
  โ†’ `hermes skill audit` checks for known vulnerabilities
# CI recommended usage
hermes skill install --frozen-lockfile

# Local development
hermes skill install      # Normal install, may update lock
hermes skill lock         # Update lock only
hermes skill audit        # Security audit
hermes skill outdated     # Show upgradable dependencies

Chapter Summary

This chapter covered the full spectrum of Hermes Skill version management:

  1. Semantic Versioning provides clear change semantics through MAJOR/MINOR/PATCH
  2. Dependency declarations support exact versions, ranges, Git sources, and local paths
  3. Cyclic dependency detection uses three-color DFS to accurately locate all cycles
  4. Conflict resolution offers four strategies suited to different operational contexts
  5. Atomic upgrades with backup-and-rollback ensure safe, reversible updates
  6. Lock files provide deterministic builds protected by content hashing

Review Questions

  1. With 50 direct dependencies each having 10 transitive deps, what is the time complexity of version resolution? How would you optimize it?
  2. Should skill.lock include dev dependencies? How should production deployments handle them?
  3. A Skill releases a security patch, but your lock file pins the old version. How do you respond quickly without disrupting functionality?
  4. Design a virtual environment mechanism allowing multiple projects on one machine to use different versions of the same Skill. How would you implement it?
Rate this chapter
4.7  / 5  (3 ratings)

๐Ÿ’ฌ Comments