Home / Articles

Deep Agents With SKILL.md: A LangChain Orchestrator That Loads Skills

Build a generic deep agent on LangChain that scans a skills folder, routes by SKILL.md descriptions, shares AGENTS.md memory, and gains new abilities without code changes.

Publisher: REACTAPP.TOP8 min read1811 words

This article is published in English.

Sources: Building Deep Agents + SKILL.md with Langchain

What if an agent had no fixed list of abilities and discovered what it could do only by looking around? That is the approach here: a working deep agent on LangChain that loads SKILL.md files on demand to answer queries. There are no hardcoded chains and no custom router. Just a generic orchestrator, a folder of skills and the deepagents library handling the heavy lifting.

The problem with fixed toolsets

Most LangChain tutorials show the same recipe. Define some tools, bind them to a model, build a graph and you're finished. The result is a shallow agent with a capability list frozen into its code.

The alternative is an orchestrator that doesn't know what it can do until it checks. At runtime it scans a skills/ directory, reads only the metadata up front and pulls in full instructions when a skill fits the request. Instead of hardcoding "this agent researches and writes", you put research/SKILL.md and writer/SKILL.md in a folder and the agent works out the rest. Tomorrow you can add a code-reviewer skill and it gets picked up without touching a line of code. That is the SKILL.md pattern applied to LangChain.

How the pieces fit

Four ideas drive the flow of agent.py:

  1. Planning comes first. The orchestrator always drafts a plan, turning the user's broad goal into concrete technical steps.
  2. Delegation stays generic. Instead of role-specific specialists, work goes to a general-purpose subagent with the same tools and the same access to the skill library.
  3. Knowledge arrives on demand. Neither the orchestrator nor the subagent knows the specialized workflows at startup. They find them through SkillsMiddleware, which discloses skills progressively, and load the full documentation only when needed.
  4. The workspace is enforced. Tools and skill-driven actions are confined to the ./workspace/ directory for persistence, while the root directory remains visible for configuration and skills.

The whole project is compact, and three files do the real work.

Step 1: project setup

Dependencies live in pyproject.toml:

# pyproject.toml
[project]
name = "deep-agents-langchain"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "deepagents>=0.4.12",
    "langchain-anthropic>=1.4.0",
    "langchain-openai>=1.1.12",
    "python-dotenv>=1.2.2",
    "streamlit>=1.55.0",
    "tavily-python>=0.7.23",
]

The main one is deepagents, LangChain's open-source library that formalizes the deep agent pattern. Out of the box it gives you planning through write_todos, file system tools, sub-agent spawning through task, context compression and, importantly, skill loading.

Step 2: define the tools

In agent.py, the only custom tool is a standard Tavily search wrapper:

import os
from typing import Literal
from dotenv import load_dotenv
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from tavily import TavilyClient
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend
load_dotenv()
# --- Generic Tools ---
tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
def internet_search(
    query: str,
    max_results: int = 5,
    topic: Literal["general", "news", "finance"] = "general",
):
    """Search the web for current information."""
    return tavily.search(query, max_results=max_results, topic=topic)

Everything else, meaning planning, file I/O, sub-agents and skill loading, comes from deepagents.

Step 3: the agent factory

This is where it all comes together:

def get_deep_agent():
    model = ChatAnthropic(
        model="claude-3-5-sonnet-20240620",
        temperature=0,
    )
return create_deep_agent(
        model=model,
        system_prompt="""You are a generic Deep Agent orchestrator.
1. Use `write_todos` to plan complex requests.
2. You have access to skills in the `skills/` directory.
   - You ONLY see names and descriptions initially.
   - For specialized tasks, load the matching SKILL.md first.
3. Use the `task` tool to delegate to subagents.
4. ALL outputs go to `./workspace/` exclusively.""",
        tools=[internet_search],
        skills=["./skills"],          # <-- This is the magic line
        memory=["./AGENTS.md"],
        backend=FilesystemBackend(root_dir="."),
        name="generic_deep_agent",
    )

Three parameters deserve a closer look.

skills=["./skills"] tells deepagents to scan that directory, parse the YAML frontmatter of each SKILL.md it finds and put the skill catalog into the system prompt. The agent sees something like this:

Available Skills:
- research: "Conducts comprehensive web research on any topic..."
- writer: "Specialized in writing high-quality content..."

For two skills that costs about 200 tokens. The agent reads the catalog and decides which skill to load for the request. It is pure LLM routing, with no classifiers, embeddings or regular expressions.

memory=["./AGENTS.md"] loads project-level conventions into the system prompt. It acts as shared context for the main agent and every sub-agent, a kind of "how we do things here" page for the team.

backend=FilesystemBackend(root_dir=".") gives the agent file tools (read_file, write_file, edit_file) with the project folder as their root, so the file system becomes the agent's lasting workspace.

Step 4: write the skills

A deliberately simple research skill:

---
name: research
description: >
  Conducts comprehensive web research on any topic using search APIs
  and web scraping. Use when the user asks for research, information
  gathering, competitive analysis, or needs current data from the web.
license: MIT
compatibility: Requires internet access and Tavily API key
allowed-tools: internet_search Read Write
---
# Web Research Skill
## Instructions
1. Parse the research query to identify key topics.
2. Use the `internet_search` tool to gather initial results.
3. If results are insufficient, try alternative search queries.
4. Synthesize findings into a structured summary.
5. Save detailed findings to `./workspace/` as a markdown file.

And a writer skill:

---
name: writer
description: >
  Specialized in writing high-quality, creative, and technical content.
  Use when the user needs a blog post, report, technical guide,
  or any form of structured writing.
license: MIT
---
# Creative Writer Skill
## Instructions
1. Review the input notes or research data provided.
2. Define an outline (Introduction, Body, Conclusion).
3. Use a tone appropriate for the target audience.
4. Ensure all claims are backed by provided data.
5. Save the final draft to `./workspace/`.

Each file has YAML frontmatter, which works as the skill's ID card, followed by a Markdown body that serves as the playbook. The agent sees the frontmatter at level 1 and the body only at level 2. This follows the spec published at agentskills.io.

Step 5: shared memory in AGENTS.md

This file is always loaded. It tells every agent who does what and how the team operates:

# Project Context: Deep Agents Demo
## Conventions
- Skills are stored as directories under `skills/`.
- Every skill must have a `SKILL.md` file.
- Subagents should be used for complex, multi-step tasks.
- All intermediate work goes to `./workspace/`.
## Known Entities
- **Main Agent**: The orchestrator who plans and delegates.
- **Researcher**: Specialized in web search and data aggregation.
- **Writer**: Specialized in formatting and synthesizing information.

How routing really works

There is no router and no classifier, because the LLM itself does the routing. When the library builds the system prompt, it adds text along these lines:

You have access to a library of skills in the `skills/` directory.
You ONLY see names and descriptions initially.
For any specialized task, look for matching skills and use
`read_file` to load the SKILL.md before executing.
Available skills:
- research: "Conducts comprehensive web research..."
- writer: "Specialized in writing high-quality content..."

The model reads the user's message and the skill descriptions, then chooses between calling read_file("skills/research/SKILL.md") and calling nothing. The whole decision happens inside the transformer's forward pass, with no outside logic. In other words, the skill description is the routing engine, and a well-written one with trigger words and negative boundaries is all it takes.

The Streamlit front end

app.py wraps everything in a Streamlit interface that shows the agent's thinking as it happens. LangGraph's stream("updates") yields an event for each node execution, and the app parses those into tool calls, skill-loading indicators, plan updates and the final response.

Adding a skill with zero code changes

Say you want a code review skill. Create a folder for it next to the others:

skills/
├── research/
│   └── SKILL.md
├── writer/
│   └── SKILL.md
└── code-reviewer/
    └── SKILL.md

Then write its SKILL.md:

---
name: code-reviewer
description: >
  Reviews code for bugs, security issues, and best practices.
  Use when the user asks for a code review, security audit,
  or wants feedback on code quality. Do NOT use for writing
  new code or debugging runtime errors.
license: MIT
allowed-tools: Read Write
---
# Code Review Skill
## Instructions
1. Read the target file using `read_file`.
2. Analyze for: bugs, security issues, performance, readability.
3. Write a structured review to `./workspace/review.md`.

Restart the app and the agent knows how to review code. agent.py is untouched and so is the system prompt. You added a folder, nothing more. The same approach leads toward more sophisticated deep agents.

Lessons that came out of it

  • Skill descriptions matter more than skill instructions. If the description is weak, the skill never fires. Tuning the description field, with its trigger words and negative boundaries such as "Do NOT use for...", took longer than writing the skill body, because that text is the routing logic.
  • The file system is underrated. Many agent tutorials keep everything in message history, which suits toys and fails on multi-step work. Saving intermediate results to ./workspace/ and letting sub-agents read them is what lets the pieces combine properly.
  • AGENTS.md ties things together. Lacking it, the orchestrator and its sub-agents each work alone. With it they share conventions, know the project layout and avoid duplicated effort, a small file with outsized impact.
  • Start generic and specialize through skills. It is tempting to cram domain knowledge into the system prompt, but keeping the orchestrator generic and putting every specialization in SKILL.md files makes the system modular and testable. Skills can be swapped without touching the agent.

What a production version would add

This app is deliberately minimal. A real deployment would likely include persistent memory through LangMem for episodic and semantic recall across sessions, MCP integration for enterprise tools such as Slack, Jira and databases, human-in-the-loop checkpoints using LangGraph's interrupt mechanism, context compression for long research jobs and, of course, more skills, each just a folder with a SKILL.md.

The direction of travel seems clear. Building agents is less about writing more code and more about writing better SKILL.md files. The framework handles the plumbing, the skills carry the expertise and the LLM works out when to use which.