Building AI Agents from Scratch (Part 4): From Atoms to Business Loops — Building and Using Agent Skills
[TL;DR / Core Concept] What is an Agent Skill? In AI Agent architecture, base tools and dedicated skills are fundamentally different. Tools are single, mostly stateless atomic APIs (web search, calculators, database queries). Skills are packaged units of “prompts + business logic + multiple tools.” Skills turn scattered capabilities into reusable, enterprise-grade assets that solve real business problems.
Welcome to Part 4 of this series. In the first three parts we mastered the ReAct loop and built complex workflows with LangGraph. But with a “brain” (LLM) and “hands” (tools), your agent is still an empty shell—it still needs craft. Today we will package atomic capabilities into productive Skills—without heavy frameworks—and build a dynamic skill library.
1. What Is a Skill? From Atomic Tools to a Business Closed Loop
Many developers dump hundreds of APIs into the model and hope it composes complex business logic on its own. That usually fails in production.
Atomic tools are the knife, pan, and stove. A Skill is the full recipe: how to cut, control heat, and balance seasoning.
For example, “fetch page HTML” is a tool. Chaining “fetch → extract DOM → audit against WCAG → format output” becomes a high-level Web Accessibility Report skill. Likewise, “read code” is a tool; combining prompts and context for dev task estimation is a high-value dedicated skill.
| Dimension | Atomic Tools | Dedicated Skills |
|---|---|---|
| Granularity | Single API / function call | Multi-step business loop |
| State | Usually stateless | Can carry intermediate results & context |
| Composition | Signature + implementation | Prompts + logic + tool orchestration |
| Reuse | Across any agent | Declaratively shared across teams / agents |
| Failure mode | Model freestyle composition drifts | Pipelined steps are debuggable |
2. Creating Skills: Framework-Free Prompt Chaining
You do not need a heavy orchestration framework to ship a reliable skill. The most effective pattern is Prompt Chaining: each step takes the previous output and transforms it.
Task Decomposition & Pipelining
For an “automated meeting follow-up” skill, do not force one giant prompt to do everything. Split a pipeline:
- Summarizer — messy notes → structured summary under a strict system prompt.
- Action Item Extractor — summary → per-person tasks & deadlines as JSON.
- Email Drafter — action items → professional follow-up email with tone constraints.
from typing import Callable
def llm_call(system: str, user: str) -> str:
"""One LLM call with a system prompt (pseudocode)."""
return client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
).choices[0].message.content
def prompt_chain(steps: list[tuple[str, Callable[[str], str]]], raw: str) -> str:
"""Pipe a list of (name, transform) steps."""
data = raw
for name, fn in steps:
print(f"→ step: {name}")
data = fn(data)
return data
SUMMARIZER = """You are a meeting-notes expert. Produce a structured summary:
- Topic
- Key decisions (bullet list)
- Open questions
Keep it under 300 words. Use Markdown."""
EXTRACTOR = """You are a task breakdown expert. Extract action items only.
Return a valid JSON array. Each item must include:
owner, task, deadline (null if unknown).
No explanatory text."""
DRAFTER = """You are a concise business communication expert.
Draft a follow-up email from the action-item JSON: professional tone, short paragraphs, clear CTA.
Return plain Markdown (no code fences)."""
def make_meeting_skill(transcript: str) -> str:
return prompt_chain(
[
("summarize", lambda x: llm_call(SUMMARIZER, x)),
("extract", lambda x: llm_call(EXTRACTOR, x)),
("draft_email", lambda x: llm_call(DRAFTER, x)),
],
transcript,
)Linked task steps are powerful and easy to debug—no complex framework required.
Strict Boundaries & Constraints
Skill quality is often decided by system prompts, not Python. Write prompts like function signatures:
- Clear role — e.g. “You are a concise business communication expert.”
- Explicit output format — JSON vs Markdown.
- Hard constraints — length limits, must-include / must-exclude rules for more deterministic behavior.
3. Using & Managing Skills: Register, Reuse, Dynamically Route
As your agent learns more crafts, skill management becomes an engineering problem.
Registration & Reuse
To share skills across teams and agents, standardize them. AGENTS.md is emerging as an open practice, supported by tools such as OpenAI Codex and Cursor.
Declarative skill configs should capture triggers, inputs, processing logic, and expected outputs. Then a “support agent” and a “sales agent” can share the same “look up refund policy” skill.
from dataclasses import dataclass
from typing import Any, Callable
@dataclass
class Skill:
id: str
name: str
description: str # for semantic retrieval & system prompts
trigger: str # when to enable
inputs: dict[str, str]
outputs: dict[str, str]
run: Callable[..., Any]
class SkillRegistry:
def __init__(self):
self._skills: dict[str, Skill] = {}
def register(self, skill: Skill) -> None:
self._skills[skill.id] = skill
def get(self, skill_id: str) -> Skill:
return self._skills[skill_id]
def list_descriptions(self) -> list[tuple[str, str]]:
return [(s.id, s.description) for s in self._skills.values()]
registry = SkillRegistry()
registry.register(
Skill(
id="meeting_followup",
name="Automated meeting follow-up",
description="Turn meeting notes into a summary, action-item JSON, and follow-up email",
trigger="User provides meeting notes and asks to summarize / extract tasks / draft email",
inputs={"transcript": "Raw meeting transcript"},
outputs={"email_draft": "Follow-up email in Markdown"},
run=make_meeting_skill,
)
)A shared skill manifest might look like:
## Skill: meeting_followup
- Trigger: User pastes meeting notes and asks for follow-up
- Inputs: transcript (string)
- Outputs: email_draft (markdown)
- Logic: summarize → extract_action_items → draft_emailDynamic Routing: Semantic Skill Discovery
The enterprise hard problem: with dozens or hundreds of skills, dumping every description into the system prompt blows the context window and scatters attention—wrong skills get called.
The fix is semantic retrieval + dynamic loading:
- Embed skill descriptions and store them in a vector-capable index.
- Match on user intent — e.g. “Why hasn’t my order shipped?” runs similarity search first.
- Load only Top-K (e.g. Top-3) into the current LLM context.
import numpy as np
def embed(text: str) -> np.ndarray:
"""Call an embedding API (pseudocode)."""
return np.array(embedding_client.embed(text))
class SemanticSkillRouter:
def __init__(self, registry: SkillRegistry):
self.registry = registry
self.index: list[tuple[str, np.ndarray]] = []
for skill_id, desc in registry.list_descriptions():
self.index.append((skill_id, embed(desc)))
def discover(self, user_intent: str, top_k: int = 3) -> list[Skill]:
q = embed(user_intent)
scored = []
for skill_id, vec in self.index:
score = float(np.dot(q, vec) / (np.linalg.norm(q) * np.linalg.norm(vec)))
scored.append((score, skill_id))
scored.sort(reverse=True)
return [self.registry.get(sid) for _, sid in scored[:top_k]]
def build_agent_system_prompt(user_query: str, router: SemanticSkillRouter) -> str:
skills = router.discover(user_query, top_k=3)
catalog = "\n".join(
f"- `{s.id}`: {s.description} (trigger: {s.trigger})" for s in skills
)
return f"""You are an enterprise business agent. Use only the skills below; do not invent others:
{catalog}
If a skill is needed, return JSON:
{{"skill_id": "...", "args": {{...}}}}
Otherwise answer the user directly."""Dynamic routing cuts token use and improves skill-selection accuracy in complex workflows.
User intent
│
▼
Embedding query
│
▼
Skill vector index (Top-K)
│
▼
Inject relevant Skills → LLM decide & executeFrequently Asked Questions (FAQ)
Q: Can Skills and Tools both be exposed to the LLM? A: Yes, but prefer layers: Tools stay inside skill implementations; expose Skills at the agent boundary. Mixing atomic APIs and business skills in one catalog increases mis-routing.
Q: What if one step in the prompt chain returns bad output?
A: Add validators between steps (e.g. json.loads + field checks). On failure, retry that step with the error—don’t always replay the whole pipeline.
Q: Do I need a vector database for semantic discovery? A: For small catalogs (< ~30 skills), in-memory cosine similarity is enough. Scale to pgvector/Qdrant later. The key idea is “retrieve then inject,” not “stuff everything into the prompt.”
📢 Preview for the Next Article Skills teach your agent how to work; Memory teaches it how to remember you. In “Part 5: Long-Term Memory — Engineering Agent Memory,” we will build semantic and procedural memory so agents keep preferences and learn from past mistakes.