Composable Skills: Building Modular AI Systems
Master the art of composable AI skills. Learn patterns for building modular, reusable skill components that combine into powerful workflows.
Composable Skills: Building Modular AI Systems
The most powerful AI systems are not monolithic giants—they are elegantly composed of smaller, focused components that work together seamlessly. Just as Unix philosophy advocates for programs that do one thing well and combine through pipes, composable AI skills create value through thoughtful combination.
In this guide, we will explore the principles and patterns of composable skill design. You will learn how to build skills that are modular by design, easy to combine, and greater than the sum of their parts when working together.
The Case for Composability
Before diving into patterns, let us understand why composability matters for AI skills.
The Monolith Problem
Consider a traditional approach to building a code review skill. A monolithic design might:
# Monolithic Code Reviewer
You are a comprehensive code reviewer. When given code:
1. Check for syntax errors
2. Analyze security vulnerabilities
3. Evaluate performance implications
4. Assess code style
5. Suggest improvements
6. Generate a summary report
7. Create GitHub comments
8. Update tracking systems
This monolithic skill works, but it has problems:
- Hard to maintain: Changes to one part affect everything
- Difficult to test: You cannot test security analysis in isolation
- Limited reuse: Cannot use the security check for other purposes
- All or nothing: Users cannot run just the parts they need
- Scaling issues: Cannot optimize different parts independently
The Composable Solution
A composable approach breaks this into focused skills:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Syntax │ │ Security │ │ Performance │
│ Checker │ │ Analyzer │ │ Profiler │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌──────┴──────┐
│ Review │
│ Aggregator │
└──────┬──────┘
│
┌──────┴──────┐
│ Report │
│ Generator │
└─────────────┘
Each component can be:
- Developed independently
- Tested in isolation
- Reused in other contexts
- Replaced without affecting others
- Scaled based on need
Principles of Composable Design
Effective composability follows several key principles.
Single Responsibility
Each skill should have one clear purpose. This is the foundation of composability.
# Good: Single Responsibility
---
name: extract-functions
description: Extracts function signatures from source code
---
# Extract Functions
You are a code parser that extracts function signatures.
Given source code, output a JSON array of functions:
- name: function name
- parameters: array of parameter names and types
- returnType: return type if specified
- lineNumber: starting line
Do not analyze the functions. Do not suggest improvements.
Just extract and structure the data.
# Bad: Multiple Responsibilities
---
name: function-everything
description: Extracts, analyzes, and refactors functions
---
# Function Everything
Extract functions, analyze their complexity,
suggest refactoring, and update documentation...
Well-Defined Interfaces
Composable skills need clear input/output contracts that other skills can rely on.
## Interface Contract
### Input
```typescript
interface FunctionExtractorInput {
code: string; // Source code to parse
language: string; // Programming language
includePrivate: boolean; // Include private functions
}
Output
interface FunctionExtractorOutput {
functions: Array<{
name: string;
parameters: Parameter[];
returnType: string | null;
lineNumber: number;
visibility: 'public' | 'private' | 'protected';
}>;
metadata: {
totalFunctions: number;
parseTime: number;
language: string;
};
}
This interface is guaranteed and will not change without version update.
### Statelessness
Composable skills should be stateless whenever possible. State makes composition harder.
```markdown
# Stateless Design
This skill:
- Takes all required information as input
- Produces complete output without side effects
- Does not remember previous invocations
- Does not depend on external state
If context is needed, it must be passed in:
```json
{
"currentFile": "path/to/file.ts",
"projectContext": { ... },
"previousAnalysis": { ... }
}
### Idempotency
Running a skill multiple times with the same input should produce the same output.
```markdown
## Idempotency Guarantee
Given identical inputs, this skill will always produce identical outputs.
This enables:
- Safe retries on failure
- Caching of results
- Parallel execution
- Predictable testing
Composition Patterns
Let us explore specific patterns for combining skills effectively.
Pipeline Pattern
Skills connected in sequence, each transforming the output of the previous.
Input → [Skill A] → [Skill B] → [Skill C] → Output
---
name: code-quality-pipeline
description: Runs code through quality checks in sequence
---
# Code Quality Pipeline
Execute this pipeline in sequence:
## Stage 1: Parse
Use `extract-ast` skill to parse code into AST
## Stage 2: Analyze
Use `complexity-analyzer` skill on the AST
## Stage 3: Check Style
Use `style-checker` skill on original code with AST context
## Stage 4: Generate Report
Use `report-generator` with all previous outputs
Pass outputs forward:
- Stage 2 receives Stage 1 output
- Stage 3 receives both Stage 1 and 2 outputs
- Stage 4 receives all previous outputs
Implementation considerations:
- Each stage must complete before the next begins
- Errors in early stages prevent later stages
- Include error handling between stages
- Consider checkpointing for long pipelines
Fan-Out/Fan-In Pattern
Distribute work to multiple skills, then aggregate results.
┌─→ [Skill A] ─┐
│ │
Input ──→ Fan-Out ─→ [Skill B] ─→ Fan-In ──→ Output
│ │
└─→ [Skill C] ─┘
---
name: multi-language-analyzer
description: Analyzes code across multiple languages in parallel
---
# Multi-Language Analyzer
## Fan-Out Phase
For each file in the input:
1. Detect the language
2. Route to appropriate analyzer:
- Python files → `python-analyzer`
- JavaScript files → `javascript-analyzer`
- TypeScript files → `typescript-analyzer`
- Go files → `go-analyzer`
Run analyzers in parallel when possible.
## Fan-In Phase
Collect all analyzer outputs and:
1. Normalize to common format
2. Aggregate metrics
3. Identify cross-language issues
4. Generate unified report
Output includes per-language and aggregate results.
Decorator Pattern
Wrap a skill to add capabilities without modifying it.
---
name: cached-analyzer
description: Adds caching to any analyzer skill
---
# Cached Analyzer
Wraps another analyzer with caching capability.
## Before Calling Wrapped Skill
1. Generate cache key from input hash
2. Check if result exists in cache
3. If cached and not expired, return cached result
4. Otherwise, proceed to wrapped skill
## After Calling Wrapped Skill
1. Receive result from wrapped skill
2. Store in cache with TTL
3. Return result to caller
## Configuration
```json
{
"wrappedSkill": "complexity-analyzer",
"cacheTTL": 3600,
"cacheInvalidateOn": ["file_modified", "manual"]
}
### Fallback Pattern
Try multiple skills in order until one succeeds.
```markdown
---
name: resilient-parser
description: Attempts multiple parsing strategies
---
# Resilient Parser
Try parsing strategies in order until success:
## Strategy 1: Native Parser
Use language-specific parser for best accuracy.
If fails, proceed to Strategy 2.
## Strategy 2: Tree-sitter Parser
Use generic tree-sitter grammar.
If fails, proceed to Strategy 3.
## Strategy 3: Regex Fallback
Use pattern matching for basic extraction.
If fails, proceed to Strategy 4.
## Strategy 4: LLM Parse
Ask LLM to extract structure from code.
This should almost always succeed.
Return result from first successful strategy.
Include metadata about which strategy was used.
Filter Pattern
Remove or transform elements before passing to next skill.
---
name: code-filter
description: Filters code elements before analysis
---
# Code Filter
Apply filters before passing to downstream skills:
## Available Filters
1. **Size Filter**: Remove files over N lines
2. **Type Filter**: Include only specified file types
3. **Pattern Filter**: Exclude files matching patterns
4. **Content Filter**: Exclude generated/minified code
5. **Recency Filter**: Only include recently modified
## Usage
```json
{
"input": [...files],
"filters": [
{ "type": "size", "maxLines": 1000 },
{ "type": "pattern", "exclude": ["*.min.js", "*.generated.*"] },
{ "type": "content", "excludeGenerated": true }
],
"downstream": "complexity-analyzer"
}
Output filtered set to downstream skill.
## Building Composable Skills
Now let us look at practical techniques for building skills that compose well.
### Standard Data Formats
Use consistent data formats across skills to enable easy composition.
```markdown
## Standard Formats
### Code Location
```json
{
"file": "path/to/file.ts",
"line": 42,
"column": 10,
"endLine": 45,
"endColumn": 3
}
Code Issue
{
"id": "unique-issue-id",
"severity": "error" | "warning" | "info",
"category": "security" | "performance" | "style" | "bug",
"message": "Human readable message",
"location": { ...CodeLocation },
"suggestion": "How to fix",
"references": ["url1", "url2"]
}
Analysis Result
{
"success": boolean,
"issues": [...CodeIssue],
"metrics": { ...arbitrary metrics },
"metadata": {
"analyzer": "skill-name",
"version": "1.0.0",
"duration": 1234,
"timestamp": "ISO-8601"
}
}
### Transformation Functions
Build skills that transform between formats to enable composition.
```markdown
---
name: eslint-to-standard
description: Transforms ESLint output to standard issue format
---
# ESLint to Standard Transformer
Transform ESLint JSON output to standard issue format.
## Input Format (ESLint)
```json
{
"filePath": "/path/to/file.js",
"messages": [{
"ruleId": "no-unused-vars",
"severity": 2,
"message": "...",
"line": 10,
"column": 5
}]
}
Output Format (Standard)
{
"issues": [{
"id": "eslint-no-unused-vars-file.js-10-5",
"severity": "error",
"category": "style",
"message": "...",
"location": {
"file": "/path/to/file.js",
"line": 10,
"column": 5
}
}]
}
Mapping Rules
- severity 2 → "error"
- severity 1 → "warning"
- Category inferred from rule prefix
### Metadata Propagation
Ensure metadata flows through skill chains for traceability.
```markdown
## Metadata Handling
Each skill must:
1. **Accept metadata**: Read incoming metadata object
2. **Preserve chain**: Keep previous skill metadata
3. **Add own metadata**: Append this skill's metadata
4. **Pass forward**: Include full chain in output
Example chain:
```json
{
"result": { ... },
"metadata": {
"chain": [
{ "skill": "file-reader", "version": "1.0.0", "duration": 50 },
{ "skill": "parser", "version": "2.1.0", "duration": 120 },
{ "skill": "analyzer", "version": "1.5.0", "duration": 340 }
],
"totalDuration": 510,
"initiatedAt": "2025-01-15T10:30:00Z"
}
}
### Error Propagation
Handle errors in ways that preserve composability.
```markdown
## Error Handling for Composition
When an error occurs:
1. **Wrap, don't throw**: Return structured error
2. **Include context**: Where in the chain did it fail
3. **Preserve partial**: Return any successful results
4. **Enable recovery**: Provide enough info to retry
Error format:
```json
{
"success": false,
"error": {
"skill": "parser",
"type": "ParseError",
"message": "Unexpected token at line 42",
"recoverable": true,
"partialResult": { ... }
},
"metadata": { ... chain so far ... }
}
Downstream skills should:
- Check success flag
- Handle gracefully if possible
- Propagate if not
## Advanced Composition Techniques
For complex systems, these advanced techniques enable sophisticated composition.
### Dynamic Composition
Select and combine skills at runtime based on input.
```markdown
---
name: dynamic-analyzer
description: Selects appropriate analyzers based on input
---
# Dynamic Analyzer
Analyze input to determine optimal skill combination.
## Analysis Phase
Examine input for:
- Languages present
- Project structure
- Configuration files
- Existing analysis results
## Skill Selection
Based on analysis, select from:
| Condition | Skills to Use |
|-----------|---------------|
| Has package.json | npm-audit, dependency-check |
| Has Dockerfile | container-security, layer-analyzer |
| Has CI config | ci-integration, pipeline-validator |
| Large codebase | sampled-analysis, parallel-dispatch |
## Composition
Compose selected skills using appropriate pattern:
- Independent skills → parallel fan-out
- Dependent skills → sequential pipeline
- Similar skills → best-of with voting
Return unified result with skill selection rationale.
Recursive Composition
Skills that can invoke themselves on subproblems.
---
name: recursive-summarizer
description: Summarizes content of any length through recursion
---
# Recursive Summarizer
Summarize content regardless of length.
## Base Case
If content is under 1000 tokens:
- Summarize directly
- Return summary
## Recursive Case
If content exceeds 1000 tokens:
1. Split into chunks of ~800 tokens
2. Summarize each chunk (recursive call)
3. Combine chunk summaries
4. If combined summaries exceed 1000 tokens, recurse
5. Return final summary
## Termination Guarantee
- Maximum recursion depth: 5
- Chunk size reduces each level
- Always converges to base case
Track recursion depth in metadata.
Conditional Composition
Compose different skills based on conditions.
---
name: conditional-processor
description: Different processing based on content type
---
# Conditional Processor
Route to different processing chains based on input.
## Detection Phase
Analyze input to determine type:
- Code: Has syntax, programming patterns
- Prose: Natural language, paragraphs
- Data: JSON, CSV, structured data
- Mixed: Combination of above
## Processing Chains
### Code Path
code-parser → linter → type-checker → summary
### Prose Path
grammar-check → style-analyzer → readability → summary
### Data Path
schema-validator → data-profiler → anomaly-detector → summary
### Mixed Path
splitter → [route each part] → aggregator → summary
Output includes which path was taken and why.
Testing Composable Skills
Composable skills need specific testing approaches.
Unit Testing Individual Skills
Test each skill in isolation with mocked inputs.
## Unit Test Suite
### Test: extract-functions
#### Happy Path
Input: Valid TypeScript file with 3 functions
Expected: Array of 3 function objects with correct metadata
#### Edge Cases
- Empty file: Should return empty array
- No functions: Should return empty array
- Arrow functions: Should extract correctly
- Nested functions: Should handle appropriately
#### Error Cases
- Invalid syntax: Should return partial results with error
- Binary file: Should reject with clear error
Integration Testing Compositions
Test skill combinations work correctly together.
## Integration Test Suite
### Test: analysis-pipeline
Setup: Create temp directory with test files
#### Pipeline Test
1. Run full pipeline on test files
2. Verify each stage produces expected output
3. Confirm final result includes all stage data
4. Check metadata chain is complete
#### Failure Handling
1. Inject failure at stage 2
2. Verify error propagates correctly
3. Confirm partial results from stage 1 preserved
4. Check recovery suggestions present
Contract Testing
Verify skills honor their interface contracts.
## Contract Tests
For each skill, verify:
### Input Contract
- Accepts all documented input types
- Rejects invalid inputs with clear errors
- Handles optional fields correctly
### Output Contract
- Output matches documented schema
- All required fields present
- Types are correct
- Metadata is complete
### Behavioral Contract
- Idempotent (same input = same output)
- Stateless (no side effects)
- Respects resource limits
Real-World Composition Example
Let us build a complete composable system for code quality.
---
name: code-quality-system
description: Composable code quality system
---
# Code Quality System
A fully composable code quality analysis system.
## Component Skills
### 1. file-collector
Collects files matching patterns
Output: Array of file paths with metadata
### 2. language-detector
Detects programming language of files
Output: Map of file path to language
### 3. syntax-validator
Validates syntax for detected language
Output: Syntax errors or "valid" per file
### 4. complexity-measurer
Measures cyclomatic complexity
Output: Complexity scores per function
### 5. security-scanner
Scans for security issues
Output: Security findings with severity
### 6. style-checker
Checks code style
Output: Style violations
### 7. report-generator
Generates unified report
Output: Formatted report
## Composition Configuration
```yaml
system: code-quality
version: 1.0.0
pipeline:
- stage: collect
skill: file-collector
config:
patterns: ["**/*.{js,ts,py}"]
exclude: ["**/node_modules/**"]
- stage: detect
skill: language-detector
input: collect.output
- stage: validate
skill: syntax-validator
input:
files: collect.output
languages: detect.output
parallel: true
- stage: analyze
skills:
- complexity-measurer
- security-scanner
- style-checker
input: validate.valid_files
parallel: true
fan_in: true
- stage: report
skill: report-generator
input:
all_stages: true
output: final
Execution Engine
The system executes based on configuration:
- Stages run in order unless parallel specified
- Parallel stages run concurrently
- Fan-in collects parallel results
- Errors short-circuit or continue based on config
- Final output includes full trace
## Conclusion
Composability transforms how we build AI systems. Instead of monolithic skills that try to do everything, we create focused components that excel at specific tasks and combine elegantly.
Key takeaways for building composable skills:
1. **Single Responsibility**: One skill, one job, done well
2. **Clear Interfaces**: Documented input/output contracts
3. **Standard Formats**: Consistent data structures across skills
4. **Statelessness**: No hidden dependencies or side effects
5. **Error Handling**: Graceful degradation with partial results
The patterns we explored—pipeline, fan-out/fan-in, decorator, fallback, and filter—provide building blocks for complex systems. Choose patterns based on your specific needs and combine them as needed.
As you build your skill library, think about composition from the start. A skill that works well alone but cannot combine with others provides limited value. A skill designed for composition becomes increasingly valuable as your library grows.
The future of AI development is modular, composable, and built on skills that work together. Start building that future today.