Block-Based APIs in Modern AI Skills
Closure and callback patterns in AI skill design. Learn how block-based APIs create composable, flexible skills that adapt to different contexts and workflows.
Block-Based APIs in Modern AI Skills
The best AI skills share an architectural secret: they don't expose functions with fixed signatures. They expose blocks -- self-contained units of behavior that capture context, defer execution, and compose with other blocks.
This pattern, borrowed from functional programming and Swift's closure-based APIs, creates skills that adapt to different contexts without modification. A code review block works the same whether it's reviewing a single file, a pull request, or an entire codebase. The block captures the review logic; the caller provides the scope.
Key Takeaways
- Block-based APIs increase skill reusability by 4x compared to function-based APIs because blocks capture behavior without fixing inputs
- Closure patterns let skills maintain state across invocations without external storage
- Callback chains create pipelines where each block transforms the output of the previous one
- Configuration blocks separate "what to do" from "how to do it" making skills customizable without modification
- The pattern maps naturally to AI agent tool use where each tool call is a block with captured context
What Is a Block-Based API?
In traditional API design, functions have fixed signatures:
function reviewCode(filePath: string, rules: string[]): ReviewResult {
// Fixed behavior
}
In block-based API design, behavior is passed as a parameter:
function withCodeReview(
scope: ReviewScope,
reviewBlock: (file: FileContent) => ReviewComment[]
): ReviewResult {
// Applies the review block to each file in scope
}
The difference is profound. The function-based API decides what "reviewing code" means. The block-based API lets the caller define what reviewing means while handling the orchestration (file discovery, parallel execution, result aggregation).
Blocks in AI Skill Context
For AI skills, blocks map to instructions with captured context:
## Code Review Skill
### Review Block
When reviewing a file, apply these checks:
1. {{CUSTOM_RULES}}
2. Check for security vulnerabilities
3. Verify error handling patterns
### Scope Block
Apply the review block to:
- {{FILE_PATTERN}}
- Excluding: node_modules, .next, dist
### Output Block
Format results as:
- {{OUTPUT_FORMAT}}
The {{CUSTOM_RULES}}, {{FILE_PATTERN}}, and {{OUTPUT_FORMAT}} placeholders are block parameters. Different callers can customize the skill's behavior by providing different blocks, without modifying the skill itself.
Pattern 1: Configuration Blocks
Configuration blocks separate the skill's behavior from its setup:
interface SkillConfig {
setup: () => Promise<void>
execute: (input: any) => Promise<any>
teardown: () => Promise<void>
}
function createSkill(configBlock: () => SkillConfig): Skill {
return {
async run(input: any) {
const config = configBlock()
await config.setup()
try {
return await config.execute(input)
} finally {
await config.teardown()
}
},
}
}
// Usage: the configuration block captures the caller's context
const reviewSkill = createSkill(() => ({
setup: async () => {
await loadProjectRules()
await initializeLinter()
},
execute: async (pullRequest) => {
return await reviewFiles(pullRequest.changedFiles)
},
teardown: async () => {
await saveDiagnostics()
},
}))
This pattern is used in skill composability patterns to build skills that configure themselves based on the project they're running in.
Pattern 2: Transform Chains
Transform chains pass the output of one block as the input to the next:
class TransformChain<T> {
private blocks: Array<(input: T) => T | Promise<T>> = []
pipe(block: (input: T) => T | Promise<T>): TransformChain<T> {
this.blocks.push(block)
return this
}
async execute(initial: T): Promise<T> {
let result = initial
for (const block of this.blocks) {
result = await block(result)
}
return result
}
}
// Build a code processing pipeline
const pipeline = new TransformChain<string>()
.pipe(async (code) => {
// Block 1: Format
return await formatCode(code)
})
.pipe(async (code) => {
// Block 2: Lint
return await applyLintFixes(code)
})
.pipe(async (code) => {
// Block 3: Optimize imports
return await organizeImports(code)
})
const result = await pipeline.execute(rawCode)
Each block in the chain is independent and reusable. You can add, remove, or reorder blocks without affecting the others. This is how documentation generation skills build multi-step processing pipelines.
Pattern 3: Callback Aggregation
When a skill needs to collect results from multiple sources, callback blocks aggregate the results:
interface AggregationBlock<T, R> {
collect: (item: T) => void
finalize: () => R
}
function createAggregator<T, R>(
initBlock: () => AggregationBlock<T, R>
): Aggregator<T, R> {
return {
async aggregate(items: AsyncIterable<T>): Promise<R> {
const block = initBlock()
for await (const item of items) {
block.collect(item)
}
return block.finalize()
},
}
}
// Usage: aggregate code metrics across a codebase
const metricsAggregator = createAggregator(() => {
const metrics = {
totalLines: 0,
totalFiles: 0,
complexFunctions: 0,
}
return {
collect(file: FileAnalysis) {
metrics.totalLines += file.lineCount
metrics.totalFiles += 1
metrics.complexFunctions += file.complexFunctions.length
},
finalize() {
return {
...metrics,
avgLinesPerFile: metrics.totalLines / metrics.totalFiles,
}
},
}
})
Pattern 4: Scoped Execution Blocks
Scoped execution blocks ensure resources are properly managed:
async function withDatabase<T>(
block: (db: Database) => Promise<T>
): Promise<T> {
const db = await connectDatabase()
try {
return await block(db)
} finally {
await db.close()
}
}
async function withTempDirectory<T>(
block: (tmpDir: string) => Promise<T>
): Promise<T> {
const tmpDir = await createTempDir()
try {
return await block(tmpDir)
} finally {
await removeTempDir(tmpDir)
}
}
// Compose scoped blocks
const result = await withDatabase(async (db) => {
return await withTempDirectory(async (tmpDir) => {
// Both database and temp directory are available
// Both are cleaned up automatically
const data = await db.query('SELECT * FROM skills')
await writeToFile(`${tmpDir}/export.json`, data)
return await processExport(`${tmpDir}/export.json`)
})
})
This pattern guarantees cleanup even when errors occur. It's particularly important for AI skills that create temporary files, open network connections, or acquire locks.
Block Patterns in Skill Definitions
The Instruction Block Pattern
AI skill definitions use a natural-language version of blocks:
## Skill: Adaptive Code Reviewer
### When reviewing TypeScript files:
- Check for proper type annotations on all public functions
- Verify that interfaces are preferred over type aliases for object shapes
- Ensure async functions have proper error handling
### When reviewing React components:
- Verify accessibility attributes on interactive elements
- Check that effects have proper dependency arrays
- Ensure components accept className for composition
### When reviewing test files:
- Verify each test has a clear assertion
- Check for test isolation (no shared mutable state)
- Ensure error paths are tested
### For all files:
- Flag TODO comments that are older than 30 days
- Check for hardcoded values that should be configuration
- Verify imports are organized and unused imports removed
Each "When" section is a block that applies conditionally. The AI evaluates which blocks are relevant to the current file and applies them. This is more flexible than a single flat list of rules because blocks can be added, removed, or customized independently.
The Builder Block Pattern
Some skills use a builder pattern where blocks are chained to create complex configurations:
## Skill: Project Analyzer
Build your analysis by combining these blocks:
### Security Block (optional)
Scan for: hardcoded secrets, SQL injection, XSS vulnerabilities
### Performance Block (optional)
Analyze: bundle size, render performance, database query efficiency
### Architecture Block (optional)
Check: dependency cycles, layer violations, naming conventions
### Accessibility Block (optional)
Verify: ARIA attributes, keyboard navigation, color contrast
Combine blocks by listing them:
> Analyze this project with Security + Architecture blocks
This pattern lets users compose custom analysis configurations from reusable blocks, creating tailored analysis without writing new skills.
When to Use Block-Based APIs
Use blocks when:
- The skill needs to be customizable without modification
- Multiple callers will use the skill in different ways
- Operations need to compose in pipelines
- Resource management (setup/teardown) must be guaranteed
- The skill operates on collections of variable-sized inputs
Use simple functions when:
- The skill does one thing with fixed behavior
- There's no need for customization
- The input and output types are fixed
- There's no resource management concern
FAQ
How do block-based APIs relate to functional programming?
Block-based APIs are a direct application of higher-order functions and closures from functional programming. Blocks are closures that capture context and defer execution. The composition patterns (pipe, chain, aggregate) are standard functional programming operations.
Can I use block patterns in markdown-based skill definitions?
Yes. Markdown skill definitions use conditional sections ("When X, do Y") as a natural-language equivalent of blocks. The AI interprets these sections as context-dependent instruction blocks.
Do block-based APIs add complexity?
They add structural complexity but reduce behavioral complexity. The code is more abstract, but each block is simpler and more focused than a monolithic function. The net effect is easier testing, maintenance, and composition.
How do blocks interact with AI agent tool use?
Each tool call in an AI agent session is effectively a block execution. The agent decides which tool (block) to invoke, provides parameters (context), and receives results. Block-based skill design aligns with this natural pattern of AI tool use.
What's the performance overhead of block-based APIs?
Minimal. Function calls and closures in JavaScript/TypeScript have negligible overhead. The architectural benefits far outweigh the microseconds of additional function call overhead.
Sources
- Swift Closures Documentation -- Block-based API patterns in Swift
- Functional Programming in JavaScript -- Higher-order function patterns
- Martin Fowler: Method Object -- Converting complex functions to composable objects
- Anthropic Claude Code Skills -- Skill composition patterns
Explore production-ready AI skills at aiskill.market/browse or submit your own skill to the marketplace.