Your AI Coding Agent Doesn't Need All 30 Skills Loaded at Once
Two-tier progressive discovery keeps coding agents like Claude Code fast by loading skill instructions only when a task actually needs them.
Your AI Coding Agent Doesn't Need All 30 Skills Loaded at Once
Give a coding agent 30 custom skills and a naive implementation will stuff every single one's full instructions into the system prompt, every turn. That's 40,000+ tokens gone before the agent has read your actual request, and it happens whether you're asking about a Postgres migration or just want a commit message.
I've hit this wall building skill-based tooling myself: the fix isn't fewer skills, it's changing when their instructions load.
The context bloat problem is a loading problem, not a skills problem
Teams building on Claude Code, Gemini CLI, and similar agentic loops are moving off monolithic system prompts and onto modular skills — self-contained instruction sets the agent can pull in as needed. The idea is sound. The failure mode shows up in the implementation.
Dump full documentation for every skill into context on every turn and you get three predictable problems: the context window fills up before real work starts, ambiguous skill definitions push the agent toward hallucinated parameters or side effects it never verified, and skills running unsandboxed shell scripts against your project files with no rollback path can quietly wreck a local environment.
None of these are reasons to avoid skills. They're reasons to be deliberate about architecture.
Two-tier progressive discovery: load names first, instructions later
This is the actual fix, and it's simpler than it sounds. Split loading into two tiers.
Tier 1 — the manifest. The agent's initial prompt gets a lightweight catalog: skill name plus a one-line description, roughly 20 tokens per skill.
skills:
- name: git-release-manager
description: Automates semantic version tagging and changelog generation.
- name: postgres-migration-auditor
description: Verifies SQL migration safety and zero-downtime constraints.
Tier 2 — on-demand retrieval. Only once the agent decides a task actually needs a specific skill does it call something like lookup_skill(name). That's when the full SKILL.md and any bundled scripts load into context.
Thirty skills at 20 tokens apiece costs you 600 tokens up front instead of 40,000. The agent still has access to everything; it just isn't paying for capabilities it isn't using on a given turn.
How a production skill directory is actually structured
A skill isn't a prompt snippet pasted into a config file. Treat it as a self-contained package: instructions, deterministic helper scripts, and reference data, all versioned together.
skills/postgres-migration-auditor/
├── SKILL.md # Primary instruction file with YAML frontmatter
├── scripts/
│ └── analyze-sql.js # Deterministic script for AST-level checks
└── references/
└── safe-ddl-rules.json # Reference rules for safe PostgreSQL operations
The SKILL.md itself carries YAML frontmatter declaring required tools, then plain instructions for when and how to apply the skill:
---
name: postgres-migration-auditor
description: Validates PostgreSQL migration files against locks, table rewrites, and zero-downtime violations.
tools_required:
- vm_shell:execute_bash
---
# Postgres Migration Auditor Instructions
## When to Apply
Use this skill whenever reviewing or generating SQL files in `migrations/` or `prisma/migrations/`.
## Execution Workflow
1. Execute the bundled parsing script:
`node skills/postgres-migration-auditor/scripts/analyze-sql.js <migration_path>`
2. Check for anti-patterns:
- `ALTER TABLE ADD COLUMN` with non-null defaults on PostgreSQL < 11.
- Adding indexes without `CONCURRENTLY`.
- `DROP COLUMN` without prior application deprecation.
3. Report categorized risks (CRITICAL, WARNING, PASS) before proceeding.
Notice the explicit tool declaration and the numbered workflow. That specificity is what keeps the agent from guessing at parameters or skipping the verification step.
Don't let the model do math it'll get wrong
This is the part worth internalizing even if you skip everything else here: never ask an LLM to do exact regex parsing, AST tokenization, or arithmetic purely through text generation. It'll be confident and it'll sometimes be wrong, which is worse than an error it flags.
Split responsibilities instead:
- The model's job: understand intent, extract parameters, write the human-readable report.
- The script's job: run the actual deterministic logic — linting rules, schema queries, compilation — inside a sandbox.
Here's what that Postgres analyzer script looks like as a small Node.js CLI tool instead of a Python one, since JS is the more natural fit if your agent tooling and skill scripts already live in a Node/TypeScript codebase:
#!/usr/bin/env node
// scripts/analyze-sql.js
// Deterministic migration-safety checks — no LLM involved.
const fs = require("fs");
const ANTI_PATTERNS = [
{
id: "non-null-default-add-column",
// Matches ADD COLUMN ... NOT NULL DEFAULT ... without a safe default strategy
regex: /ADD\s+COLUMN\s+\w+\s+\w+.*NOT\s+NULL\s+DEFAULT/i,
severity: "CRITICAL",
message: "Adding a NOT NULL column with a default rewrites the whole table on PostgreSQL < 11.",
},
{
id: "index-without-concurrently",
regex: /CREATE\s+(UNIQUE\s+)?INDEX\s+(?!CONCURRENTLY)/i,
severity: "WARNING",
message: "Index creation without CONCURRENTLY locks writes for the duration.",
},
{
id: "drop-column",
regex: /DROP\s+COLUMN/i,
severity: "WARNING",
message: "Dropping a column without a prior deprecation window can break in-flight app instances.",
},
];
function analyze(sql) {
const findings = ANTI_PATTERNS
.filter(({ regex }) => regex.test(sql))
.map(({ id, severity, message }) => ({ id, severity, message }));
return findings.length
? findings
: [{ id: "none", severity: "PASS", message: "No known anti-patterns detected." }];
}
const migrationPath = process.argv[2];
if (!migrationPath) {
console.error("Usage: node analyze-sql.js <migration_path>");
process.exit(1);
}
const sql = fs.readFileSync(migrationPath, "utf8");
const results = analyze(sql);
console.log(JSON.stringify(results, null, 2));
const hasCritical = results.some((r) => r.severity === "CRITICAL");
process.exit(hasCritical ? 1 : 0);
The agent never touches the regex logic or the exit-code convention. It runs the script, reads structured output, and turns that into a report. If you need real AST parsing instead of regex heuristics, swap in node-sql-parser or pgsql-ast-parser — the point isn't the parsing method, it's that the model calls a script instead of "thinking through" the SQL itself.
The checklist I'd actually pin to a PR template
| Layer | Best practice | Antipattern |
|---|---|---|
| Context loading | Two-tier progressive discovery (lookup_skill) |
Dumping 50 skill files into the system prompt |
| Logic execution | Bundled deterministic scripts (scripts/*.js) |
Pure LLM regex/math generation |
| Configuration | Explicit YAML frontmatter with strict tool declarations | Untyped markdown text |
| Safety | Read-only analysis before write actions | Blind mutating file execution |
If I had to rank these, context loading is the one people skip first and pay for last — it works fine with three skills and quietly degrades as the catalog grows, so the cost shows up months after the design decision that caused it.
The rest is mostly discipline: declare your tools explicitly, keep scripts deterministic, and don't let a skill mutate files before it's told you what it found.
Accelerate your AI Skills & Agentic Tools Modernization Roadmap
Need custom architecture auditing, automated OpenAPI contract generation, or zero-downtime microservice migration guidance for your engineering team?
Frequently Asked Questions
What is two-tier progressive discovery in AI agent skills?
It's a loading pattern where the agent's system prompt only gets a short name-plus-description manifest for each skill. Full instructions and scripts load on demand, only when the agent decides a task needs that specific skill.
Why shouldn't an LLM run SQL parsing or math directly?
Language models are unreliable at exact computation and structural parsing. Offload that work to a deterministic script the agent calls, and let the model handle intent, parameters, and reporting instead.
Subscribe to RenovateAPI
Get weekly architectural guides, API refactoring strategies, and technical SEO updates delivered directly to your inbox.
Discussion (2)
Extremely helpful breakdown of the Strangler Fig pattern! We're currently refactoring a legacy Java monolith at work and the OpenAPI gateway routing tips saved us weeks of experimentation.
The schema JSON-LD and FAQ block structure really helps with indexing. Great technical detail on entity mentions too.
Suggested Related Articles
Your MCP Server Works Locally But Breaks in Production — Here's Why
Local MCP servers usually fail in production for one of four reasons: transport, payload size, tool naming, or auth handshakes. Here's how to fix each.
Custom Slash Commands and Subagents for Multi-File Refactors in Claude Code
A single prompt can't safely refactor a multi-file codebase. Here's how repo-scoped slash commands and staged subagents in Claude Code fix that, with git safety gates included.
Why Your NestJS API Slows Down Under Load (And It's Probably Not the Database)
A field guide to diagnosing V8 memory leaks and event loop lag in high-throughput NestJS APIs — measuring lag with perf_hooks, killing RxJS subscription leaks, and offloading CPU work to worker threads.