Code as AI Skills (CaaS) Pattern

aiarchitecturetypescriptdeveloper-experience
By sko11/22/20255 min read

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:

  1. Write once, use everywhere — Same function works in app and AI contexts
  2. AI grows with your codebase — Every function you write becomes an AI skill
  3. 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:

  1. Write once, use everywhere — Production functions serve both app and AI
  2. No translation needed — AI imports and executes the same code
  3. Stay lean — SKILL.md provides discovery, not implementation
  4. Compounding growth — Both you and AI contribute to the skills folder

Getting Started:

  1. Create a /skills folder (sibling to /app)
  2. Add a domain folder with your function
  3. Write a minimal SKILL.md
  4. 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.


Further Resources