Version Control and Maintenance

Lesson 8 · Agentic Skills Best Practices · ~8 minutes

By the end of this lesson, you'll apply semantic versioning to agent skills, maintain changelogs that communicate impact clearly, manage dependencies across LLM model versions, and implement lifecycle management that keeps your agent ecosystem healthy over time.

Win

After this lesson, you'll have a versioning strategy that lets consumers of your skills upgrade confidently — knowing exactly what changed, what might break, and when deprecated features disappear.

Why Versioning Matters for Agent Skills

Agent skills aren't static artifacts. Models evolve, prompts get refined, tool APIs change, and new capabilities emerge. Without disciplined versioning, consumers of your skill face a grim choice: pin to an old version and miss improvements, or upgrade blindly and risk breakage in production.

The MCP specification treats servers as versioned components with explicit capability negotiation. Your skills deserve the same rigor. A version number is a contract — it tells consumers exactly how much risk an upgrade carries.

Semantic Versioning for Skills

Apply Semantic Versioning (SemVer) to agent skills with domain-specific interpretations. The format is MAJOR.MINOR.PATCH, where each segment communicates a different level of change impact:

MAJOR — Breaking Interface Changes

Increment MAJOR when consumers must modify their code or configuration to continue working. In the agent skill context, these are breaking changes:

# skill-spec.yaml — MAJOR version bump (2.x → 3.0.0)
# Breaking: renamed 'query' parameter to 'prompt', removed 'verbose' flag
name: research-agent
version: 3.0.0
interface:
  inputs:
    - name: prompt        # was 'query' in v2.x — BREAKING
      type: string
      required: true
    - name: depth         # was 'verbose' boolean in v2.x — BREAKING
      type: integer       # changed type from boolean to integer
      required: false
      default: 2

MINOR — New Features, Backward Compatible

Increment MINOR when you add functionality that existing consumers can ignore. Their code continues to work without changes:

# skill-spec.yaml — MINOR version bump (3.0.0 → 3.1.0)
# Added: optional 'sources' parameter, new 'confidence' field in output
name: research-agent
version: 3.1.0
interface:
  inputs:
    - name: prompt
      type: string
      required: true
    - name: depth
      type: integer
      required: false
      default: 2
    - name: sources       # NEW — optional, defaults to "all"
      type: string
      required: false
      default: "all"
  outputs:
    - name: result
      type: string
    - name: confidence    # NEW — consumers can ignore this field
      type: float

PATCH — Bug Fixes

Increment PATCH for fixes that don't change the interface or add features. Consumers should always feel safe upgrading patches:

# CHANGELOG entry for PATCH (3.1.0 → 3.1.1)
# Fixed: prompt truncation when input exceeds 50k tokens
# Fixed: timeout not respected when API returns partial response
# No interface changes — safe to upgrade without code modifications
Key Insight

When in doubt, bump MAJOR. A false-alarm major version bump costs consumers a few minutes reading the changelog. A stealth breaking change in a minor bump costs them hours debugging production failures.

Changelog Best Practices

A changelog is the human-readable story of your skill's evolution. The Keep a Changelog format, adapted for agent skills, uses three categories that map directly to SemVer:

Format: Added / Changed / Fixed

Category Maps to What belongs here
Added MINOR+ New features, tools, parameters, model support
Changed MAJOR or MINOR Modified behavior, renamed fields, altered defaults. Mark breaking changes explicitly.
Fixed PATCH Bug fixes, documentation corrections, edge-case handling

Additional categories when needed: Deprecated (will be removed in a future MAJOR), Removed (MAJOR only), Security (vulnerability patches).

Example Changelog

# Changelog — research-agent

## [3.1.1] - 2026-07-18

### Fixed
- Prompt truncation when input exceeds 50k tokens now chunks correctly
- Timeout handler now respects partial API responses instead of hanging
- Token counting uses cl100k_base encoding (was incorrectly using p50k)

## [3.1.0] - 2026-07-10

### Added
- Optional `sources` parameter to restrict search scope (web, internal, academic)
- `confidence` field in output (0.0–1.0) indicating result reliability
- Support for Claude 3.5 Sonnet as execution model

### Changed
- Default search depth increased from 1 to 2 (backward compatible — existing
  behavior unchanged for consumers not using the parameter)

## [3.0.0] - 2026-06-28

### Changed
- **BREAKING:** Renamed `query` parameter to `prompt` for consistency with
  MCP naming conventions. Update your invocations.
- **BREAKING:** `verbose` boolean replaced by `depth` integer (1-5).
  Migration: `verbose: true` → `depth: 3`, `verbose: false` → `depth: 1`

### Removed
- **BREAKING:** Dropped support for Claude 2.x models (EOL upstream)
- Removed deprecated `raw_mode` parameter (deprecated since 2.3.0)

### Added
- Migration guide: docs/migration-v2-to-v3.md

## [2.4.0] - 2026-06-15

### Deprecated
- `verbose` parameter — will be replaced by `depth` in v3.0.0 (target: June 28)
- Claude 2.x model support — upstream EOL announced, removal in v3.0.0
Changelog Rules

Each entry answers three questions: What changed? Why should consumers care? What do they need to do (if anything)? For breaking changes, always include migration instructions or a link to a migration guide.

Dependency Management

Agent skills depend on models, tool APIs, framework libraries, and other skills. Each dependency is a potential break point. Manage them proactively rather than reactively.

Compatibility Matrices

Document which combinations of dependencies are tested and supported. This prevents the "works on my machine" problem at scale:

# compatibility.yaml — research-agent v3.1.x
dependencies:
  models:
    - name: claude-3.5-sonnet
      versions: ["20241022", "20250101"]
      status: supported
      notes: "Primary target. All tests run against this."
    - name: claude-3-opus
      versions: ["20240229"]
      status: supported
      notes: "Higher quality, higher latency. Use for complex research."
    - name: claude-3-haiku
      versions: ["20240307"]
      status: compatible
      notes: "Works but quality degrades on multi-step tasks."
    - name: claude-2.1
      versions: ["*"]
      status: deprecated
      removal_date: "2026-06-28"
      notes: "Upstream EOL. Migrate to claude-3.5-sonnet."

  tools:
    - name: web-search-api
      versions: [">=2.0.0", "<4.0.0"]
      pinned: "3.2.1"
      notes: "v4.0 changes response schema — blocked until adapter written."
    - name: document-retriever
      versions: [">=1.5.0"]
      pinned: "1.8.0"

  frameworks:
    - name: mcp-sdk
      versions: [">=0.9.0", "<2.0.0"]
      pinned: "1.2.3"
    - name: tokenizer
      versions: [">=0.4.0"]
      pinned: "0.5.1"

  skills:
    - name: summarization-skill
      versions: [">=2.0.0", "<3.0.0"]
      notes: "Used for final output compression."

Pin Critical Dependencies

Not all dependencies are equal. Pin the ones where version drift causes silent behavior changes:

# requirements.lock — pinned for reproducibility
# CRITICAL: These affect output quality. Do not upgrade without running eval suite.
anthropic==0.34.2           # Model API client — response format can change
tiktoken==0.7.0             # Token counting — different versions count differently
mcp-sdk==1.2.3              # Protocol layer — breaking changes affect all tools

# FLEXIBLE: These can float within compatible ranges
httpx>=0.27.0,<1.0.0        # HTTP client — stable interface
pydantic>=2.5.0,<3.0.0      # Validation — breaking changes are MAJOR only
structlog>=24.1.0           # Logging — additive changes only

Test Against Multiple LLM Versions

Model providers release new versions that change behavior subtly. Your CI must test against every supported model version:

# ci/test-matrix.yaml
test_matrix:
  model_versions:
    - claude-3.5-sonnet-20241022
    - claude-3.5-sonnet-20250101
    - claude-3-opus-20240229
  
  test_suites:
    - name: regression
      description: "Core behavior hasn't changed"
      threshold: { accuracy: 0.92, latency_p95: 5000 }
    
    - name: quality
      description: "Output quality meets bar"
      threshold: { accuracy: 0.88 }
      # Lower threshold for older models — acceptable degradation
      overrides:
        claude-3-haiku-20240307: { accuracy: 0.80 }

  schedule:
    on_commit: [regression]           # fast — runs on every PR
    nightly: [regression, quality]    # full matrix — runs overnight
    on_model_release: [regression, quality]  # triggered by provider announcements

Document Deprecation Timelines

Deprecation without a timeline is a lie. Give consumers concrete dates and migration paths:

# deprecation-policy.md

## Active Deprecations

| Feature | Deprecated | Removal Target | Migration Path |
|---------|-----------|----------------|----------------|
| `verbose` param | v2.4.0 (Jun 15) | v3.0.0 (Jun 28) | Use `depth: 3` for verbose, `depth: 1` for concise |
| Claude 2.x support | v2.4.0 (Jun 15) | v3.0.0 (Jun 28) | Switch to claude-3.5-sonnet in config |
| `raw_mode` param | v2.3.0 (May 20) | v3.0.0 (Jun 28) | Use structured output with `format: json` |
| Python 3.9 support | v3.0.0 (Jun 28) | v4.0.0 (Q4 2026) | Upgrade to Python 3.11+ |

## Deprecation Rules

1. Minimum 30 days between deprecation notice and removal
2. Deprecated features emit runtime warnings with migration instructions
3. Removal only happens in MAJOR version bumps
4. Each deprecation includes a migration guide or one-line fix
5. Breaking removal is announced in the previous MINOR's changelog
Key Insight

Deprecation is a communication tool, not a punishment. The goal is giving consumers enough time and information to migrate smoothly. A surprise removal in a patch version destroys trust permanently.

Lifecycle Management

Skills and their model dependencies have lifecycles. Governance requires tracking what's approved, what's aging, and what needs proactive replacement before it breaks.

Model Catalog

Maintain a catalog of approved models organized by use case. This prevents teams from independently adopting untested models and provides a single source of truth for what's production-ready:

# model-catalog.yaml — Approved models by use case
catalog:
  text_generation:
    primary:
      model: claude-3.5-sonnet-20250101
      status: approved
      approved_date: 2025-01-15
      eval_score: 0.94
      cost_per_1k_tokens: 0.003
      notes: "Best balance of quality, speed, and cost for general tasks."
    alternatives:
      - model: claude-3-opus-20240229
        status: approved
        use_when: "Task requires highest quality; latency budget > 10s"
        eval_score: 0.97
        cost_per_1k_tokens: 0.015
      - model: claude-3-haiku-20240307
        status: approved
        use_when: "High volume, low complexity; latency budget < 1s"
        eval_score: 0.82
        cost_per_1k_tokens: 0.00025

  code_generation:
    primary:
      model: claude-3.5-sonnet-20250101
      status: approved
      approved_date: 2025-01-20
      eval_score: 0.91
      notes: "Strongest code quality across languages."

  summarization:
    primary:
      model: claude-3.5-sonnet-20250101
      status: approved
      eval_score: 0.93

  classification:
    primary:
      model: claude-3-haiku-20240307
      status: approved
      eval_score: 0.89
      notes: "Classification doesn't need heavy reasoning — haiku is cost-effective."

  retired:
    - model: claude-2.1
      status: end_of_life
      eol_date: 2026-06-01
      replacement: claude-3.5-sonnet-20250101
      migration_guide: docs/migrate-claude2-to-3.5.md

Proactive Updates at End-of-Life

Don't wait for a model to stop working. When a provider announces end-of-life, trigger migration immediately:

# lifecycle-automation.yaml
lifecycle_rules:
  - trigger: model_eol_announced
    actions:
      - create_migration_ticket:
          priority: high
          deadline: "eol_date - 30 days"
          assignee: skill_owner
          template: |
            ## Model EOL Migration Required
            
            **Model:** {{model_name}}
            **EOL Date:** {{eol_date}}
            **Replacement:** {{recommended_replacement}}
            
            ### Steps
            1. Update compatibility.yaml to add replacement model
            2. Run full eval suite against replacement
            3. Update prompts if quality degrades (score delta > 3%)
            4. Publish MINOR version with new model support
            5. Deprecate old model in changelog
            6. Publish MAJOR version removing old model (after 30-day window)
      
      - notify_consumers:
          channels: [email, slack]
          message: |
            ⚠️ {{skill_name}} v{{version}} uses {{model_name}} which reaches
            EOL on {{eol_date}}. Upgrade to v{{next_version}} (available 
            {{available_date}}) which supports {{replacement}}.
      
      - schedule_eval_run:
          model: "{{recommended_replacement}}"
          test_suite: full
          notify_on_failure: skill_owner

  - trigger: eval_score_drops_below_threshold
    conditions:
      score_delta: ">5%"
      duration: "3 consecutive runs"
    actions:
      - alert:
          severity: high
          message: "{{skill_name}} quality degraded on {{model_name}}"
      - create_investigation_ticket:
          priority: high

  - trigger: dependency_security_advisory
    actions:
      - create_patch_ticket:
          priority: critical
          deadline: "advisory_date + 7 days"

Version Lifecycle States

Every skill version moves through a defined lifecycle. Track the state explicitly:

┌──────────┐ ┌──────────┐ ┌────────────┐ ┌─────────────┐ ┌─────────┐ │ Draft │───▶│ Active │───▶│ Deprecated │───▶│ End-of-Life │───▶│ Removed │ └──────────┘ └──────────┘ └────────────┘ └─────────────┘ └─────────┘ │ │ │ │ │ │ │ │ Internal Production Warnings Errors on testing use allowed emitted on invocation only each use
# Track skill versions and their lifecycle state
skill_registry:
  research-agent:
    versions:
      - version: "3.1.1"
        state: active
        released: "2026-07-18"
        supported_until: null  # current — no planned EOL
      
      - version: "3.0.0"
        state: deprecated
        released: "2026-06-28"
        deprecated_since: "2026-07-10"
        removal_target: "2026-09-10"
        reason: "Superseded by 3.1.x with confidence scoring"
      
      - version: "2.4.0"
        state: end_of_life
        released: "2026-06-15"
        eol_since: "2026-06-28"
        reason: "MAJOR version released — v2.x no longer maintained"

Putting It Together: The Maintenance Workflow

Here's how versioning, changelogs, dependencies, and lifecycle management connect in practice:

# Maintenance workflow — triggered by upstream model release

1. Provider announces new model version
   └─▶ Lifecycle automation creates eval ticket

2. Run eval suite against new model
   ├─▶ Pass (score within 3%): Add to compatibility matrix → MINOR bump
   └─▶ Fail (score drops >3%): Investigate → fix prompts → re-eval → MINOR bump

3. Update compatibility.yaml
   └─▶ Add new model to "supported" list

4. Update CHANGELOG.md
   └─▶ "Added: Support for claude-X.Y-YYYYMMDD"

5. If old model approaching EOL:
   ├─▶ Add "Deprecated" entry to changelog (current MINOR)
   ├─▶ Emit runtime deprecation warning
   ├─▶ Notify consumers with timeline
   └─▶ Schedule MAJOR bump to remove (30+ days out)

6. Publish new version
   └─▶ Consumers upgrade at their own pace within the support window
Automation

Automate steps 1-3. Model releases are predictable events. A scheduled CI job that runs your eval suite against newly released models and opens a PR with updated compatibility data eliminates the most common maintenance bottleneck: nobody noticed the new version was available.

Verify Your Understanding

You add a new optional parameter to your skill with a sensible default value. Existing consumers don't need to change their code. What version bump is correct?

A model provider announces that Claude 3 Haiku reaches end-of-life in 60 days. Your skill currently lists it as "supported." What's the correct sequence of actions?

Your compatibility matrix shows a skill tested against two model versions. A third model version is released by the provider. Your eval suite shows quality drops 8% on the new version. What should you do?

Apply It

Pick one of your skills and create a CHANGELOG.md retroactively. Document at least three versions (even if you've been informally iterating). Then create a compatibility.yaml listing every model and tool dependency with its tested version range. If you discover untested combinations, add them to your CI matrix.

Primary Source

Read the Anthropic MCP Specification — the protocol's approach to capability negotiation and version handshakes between clients and servers demonstrates how versioning enables safe evolution of distributed agent systems without breaking existing integrations.

🤖 Ask your teacher: Want help creating a compatibility matrix or deprecation timeline for your skills? Share your current dependency list and I'll draft a maintenance plan with automation triggers.