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.
Master the art of composable AI skills. Learn patterns for building modular, reusable skill components that combine into powerful workflows.
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.
Before diving into patterns, let us understand why composability matters for AI skills.
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:
A composable approach breaks this into focused skills:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Syntax │ │ Security │ │ Performance │
│ Checker │ │ Analyzer │ │ Profiler │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌──────┴──────┐
│ Review │
│ Aggregator │
└──────┬──────┘
│
┌──────┴──────┐
│ Report │
│ Generator │
└─────────────┘
Each component can be:
Effective composability follows several key principles.
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...
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
}
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
Let us explore specific patterns for combining skills effectively.
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:
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.
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.
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
}
{
"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"]
}
{
"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
}]
}
{
"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
}
}]
}
### 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:
## 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.
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.
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.
Composable skills need specific testing approaches.
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
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
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
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
The system executes based on configuration:
## 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.
Design short-term, long-term, and graph-based memory architectures for agents