Code as AI Skills (CaaS) Pattern
What is CaaS?
Code as AI Skills (CaaS) is a pattern where your production functions become AI capabilities—without any translation layer.
The core idea: The production code IS the skill. You write a function once, add a lightweight SKILL.md file, and now both your app AND AI can use it.
┌─────────────────────────────────────────────────────────────────┐
│ THE CAAS FLYWHEEL │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Developer writes code ──────► Code becomes AI skill │
│ ▲ │ │
│ │ │ │
│ │ ▼ │
│ AI writes code ◄─────────── AI gains new capability │
│ │
│ Each iteration: Both developer AND AI become more capable │
│ │
└─────────────────────────────────────────────────────────────────┘
Three benefits:
- Write once, use everywhere — Same function works in app and AI contexts
- AI grows with your codebase — Every function you write becomes an AI skill
- AI can extend itself — AI writes code that becomes new skills
How It Works
A single function serves both your application AND AI agents:
skills/user-management/
├── SKILL.md ← AI reads this to discover the function
└── createUser.ts ← The actual function (used by both)
Your app imports it directly:
// In app/api/register/route.ts
import { createUser } from '@/skills/user-management/createUser';
const result = await createUser({ email: 'user@example.com', name: 'John' });
AI reads SKILL.md, then imports the exact same function:
// AI-generated code (identical)
import { createUser } from '@/skills/user-management/createUser';
const result = await createUser({ email: 'user@example.com', name: 'John' });
The point: No "app version" and "AI version". Same import, same function, same execution. AI just needs SKILL.md to discover it exists.
┌──────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ DEVELOPER │ │ AI AGENT │ │
│ │ │ │ │ │
│ │ Writes & imports │ │ Discovers & uses │ │
│ │ functions directly │ │ via SKILL.md │ │
│ └──────────┬──────────┘ └──────────┬──────────┘ │
│ │ │ │
│ │ import { createUser } │ reads SKILL.md │
│ │ from '@/skills/...' │ then imports │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ /skills FOLDER │ │
│ │ (Shared Code Layer) │ │
│ │ │ │
│ │ The SAME code runs whether called by app or AI │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
The Compounding Effect
AI Creates New Skills
AI can use existing skills to create new ones.
Example: You ask AI to "create a user onboarding flow"
AI discovers existing skills, then composes them:
// skills/onboarding/onboardUser.ts (AI-generated)
import { sendWelcomeEmail } from '@/skills/email/sendWelcomeEmail';
import { createUser } from '@/skills/user-management/createUser';
export async function onboardUser(email: string, name: string) {
const user = await createUser({ email, name });
if (user.success) {
await sendWelcomeEmail(email, name);
}
return user;
}
AI also generates the SKILL.md, now onboardUser is available to your app, AI, and future compositions.
You Create New Skills (Same Pattern)
You do the same thing—compose existing skills (including AI-generated ones):
// skills/admin/bulkOnboard.ts (You write this)
import { onboardUser } from '@/skills/onboarding/onboardUser';
// AI-generated
import { assignRole } from '@/skills/permissions/assignRole';
export async function bulkOnboardAdmins(
users: Array<{ email: string; name: string }>,
) {
const results = [];
for (const user of users) {
const result = await onboardUser(user.email, user.name);
if (result.success) {
await assignRole(result.userId, 'admin');
}
results.push(result);
}
return results;
}
Add the SKILL.md (or ask AI to generate it for you), and AI can now use your code too.
The Growth Loop
┌────────────────────────────────────────────────────────────────────────┐
│ │
│ DEVELOPER CONTRIBUTIONS RESULT │
│ ────────────────────── ────── │
│ │
│ • Writes new utility function → AI can now use it │
│ • Refactors existing code → AI gets improved version │
│ • Adds new domain folder → AI discovers new capability │
│ │
│ AI CONTRIBUTIONS RESULT │
│ ──────────────── ────── │
│ │
│ • AI writes new function → Developer can import it │
│ • AI generates SKILL.md → Documents its own work │
│ • AI extends existing skill → Both benefit immediately │
│ │
└────────────────────────────────────────────────────────────────────────┘
/skills folder
┌─────────────────────┐
Week 1: │ 5 functions │
└─────────────────────┘
↓
┌─────────────────────┐
Week 2: │ 12 functions │ ← Dev added 4, AI added 3
└─────────────────────┘
↓
┌─────────────────────┐
Week 4: │ 28 functions │ ← Compounding growth
└─────────────────────┘
The math: If you write 10 functions, you've created 10 AI skills. If AI writes 5 more, you now have 15 functions AND 15 AI skills. The gap between "what your app can do" and "what AI can do" becomes zero.
Setting Up Skills
Folder Structure
The /skills folder lives outside your app folder:
project-root/
├── app/ # Your application routes
├── skills/ # ← Skills live here (sibling to app)
│ ├── user-management/
│ │ ├── SKILL.md # ← Required: AI discovery
│ │ └── createUser.ts
│ ├── email/
│ │ ├── SKILL.md
│ │ └── sendWelcome.ts
│ └── {subdomain}/ # Nested skills allowed
│ └── SKILL.md
└── package.json
What's required vs flexible:
| Required | Flexible |
| ------------------------- | -------------------------------- |
| SKILL.md in each domain | How you organize .ts files |
| Domain folder name | _models/, _utils/, etc. |
| | Additional .md docs (optional) |
| | File naming conventions |
SKILL.md Format
Keep it lean (30-50 lines). AI reads these frequently.
---
name: email
description: Send emails via Resend API. Use for welcome emails, notifications.
---
## Available Functions
### sendWelcomeEmail
**Signature:** `sendWelcomeEmail(to: string, name: string): Promise<{ success }>`
**What:** Sends welcome email to new user
**Location:** `sendWelcome.ts`
## Subdomains
- **Templates:** `templates/SKILL.md` - Email template management
What goes where:
| In SKILL.md | In Separate .md Files | | ---------------------- | ---------------------- | | Function signatures | Step-by-step workflows | | One-line descriptions | Detailed code examples | | File locations | Edge cases and gotchas | | Pointers to other docs | Full API documentation |
Rule of thumb: If it helps AI decide whether to use a function → SKILL.md. If it helps AI use it correctly in complex scenarios → reference file.
Why CaaS Over Traditional Tools?
Traditional AI tools require maintaining two separate things:
┌─────────────────────────────────────────────────────────────────────┐
│ TRADITIONAL: Two things to maintain │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. JSON Schema (for AI) 2. Actual Function (for app) │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ { │ │ function createUser() { │ │
│ │ "name": "create_user",│ ←→ │ // implementation │ │
│ │ "inputSchema": {...} │ │ } │ │
│ │ } │ └─────────────────────────┘ │
│ └─────────────────────────┘ │
│ │
│ Problem: These MUST stay in sync. They always drift. │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ CAAS: One thing to maintain │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Your Function + SKILL.md (points to it) │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ function createUser() { │ ←── │ SKILL.md says: │ │
│ │ // implementation │ │ "createUser is here" │ │
│ │ } │ └─────────────────────────┘ │
│ └─────────────────────────┘ │
│ │
│ SKILL.md is just a pointer. The function IS the source of truth. │
│ │
└─────────────────────────────────────────────────────────────────────┘
| Traditional | CaaS | | ------------------------------------ | ------------------------------ | | Schema defines what AI sees | Code defines what AI sees | | Schema can lie about implementation | SKILL.md points to actual code | | Update function → must update schema | Update function → done | | Test schema + test function | Test function once |
Conclusion
Key Takeaways:
- Write once, use everywhere — Production functions serve both app and AI
- No translation needed — AI imports and executes the same code
- Stay lean — SKILL.md provides discovery, not implementation
- Compounding growth — Both you and AI contribute to the skills folder
Getting Started:
- Create a
/skillsfolder (sibling to/app) - Add a domain folder with your function
- Write a minimal SKILL.md
- Import the function in your app—AI can now use it too
The CaaS philosophy: Your codebase is already full of capabilities. Make them discoverable, and they become skills.