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:
- Semantic Versioning provides clear change semantics through MAJOR/MINOR/PATCH
- Dependency declarations support exact versions, ranges, Git sources, and local paths
- Cyclic dependency detection uses three-color DFS to accurately locate all cycles
- Conflict resolution offers four strategies suited to different operational contexts
- Atomic upgrades with backup-and-rollback ensure safe, reversible updates
- Lock files provide deterministic builds protected by content hashing
Review Questions
- With 50 direct dependencies each having 10 transitive deps, what is the time complexity of version resolution? How would you optimize it?
- Should
skill.lockinclude dev dependencies? How should production deployments handle them? - A Skill releases a security patch, but your lock file pins the old version. How do you respond quickly without disrupting functionality?
- Design a virtual environment mechanism allowing multiple projects on one machine to use different versions of the same Skill. How would you implement it?