DEV Community

UK
UK

Posted on

Modular Prompts: Managing Complex Claude Code Skills with @include and @delegate

If you've been building AI agents with Claude Code, you've probably hit this wall: your SKILL.md starts as a clean 50-line file, and three months later it's a 400-line monolith that nobody wants to touch.

There's a better way.


The Problem with Monolithic Prompts

As your Claude Code projects grow, a single SKILL.md tends to accumulate everything:

  • Role definitions
  • Domain knowledge (account codes, product catalogs, compliance rules)
  • Output format specs
  • Conditional logic for edge cases
  • Changelog notes

The result is a file that's hard to read, hard to test, and impossible to share across skills. When your "accounting assistant" and your "invoice generator" both need the same tax code definitions, you end up copy-pasting — and then maintaining two diverging copies.

Sound familiar?


The Solution: @include and @delegate

dotmd-parser introduces two directives that bring modular design to prompt engineering.

@include — inline composition

@include path/to/file.md
Enter fullscreen mode Exit fullscreen mode

At runtime, the contents of the referenced file are inserted inline — exactly like #include in C or import in Python. The final prompt seen by the model is the fully expanded text.

Example:

# Invoice Generator

@include shared/role-accountant.md
@include shared/tax-codes.md
@include shared/output-format.md

## Task
Generate an invoice for the following items: {{items}}
Enter fullscreen mode Exit fullscreen mode

Each shared file stays focused on one thing. role-accountant.md defines who the model is. tax-codes.md is your master reference for tax rates. output-format.md specifies the JSON schema you expect back.

When tax rates change, you update one file — and every skill that @includes it picks up the change automatically.

@delegate — agent composition

@delegate path/to/agent.md
@delegate path/to/agent.md --parallel
Enter fullscreen mode Exit fullscreen mode

Where @include expands content, @delegate hands off execution to a sub-agent. The referenced file is not expanded inline — it's a separate agent that runs independently (or in parallel with --parallel).

Example:

# Document Processing Pipeline

@include shared/role-orchestrator.md

## Steps
1. Extract text from uploaded document
@delegate agents/ocr-extractor.md

2. Classify document type
@delegate agents/doc-classifier.md --parallel

3. Route to appropriate handler
@delegate agents/routing-agent.md
Enter fullscreen mode Exit fullscreen mode

This is how you build multi-agent workflows without everything living in one file.


A Real-World Example: Customer Support Skill

Here's how a customer support skill looks when broken into modules:

support/
├── SKILL.md                    # Root — orchestrates everything
├── shared/
│   ├── role.md                 # "You are a support specialist..."
│   ├── product-catalog.md      # Product names, SKUs, pricing
│   ├── escalation-rules.md     # When to escalate to human
│   └── response-format.md      # JSON output schema
└── agents/
    ├── intent-classifier.md    # Classifies ticket intent
    └── kb-search.md            # Searches knowledge base
Enter fullscreen mode Exit fullscreen mode

SKILL.md ties it together:

# Customer Support Skill

@include shared/role.md
@include shared/product-catalog.md
@include shared/escalation-rules.md
@include shared/response-format.md

## Workflow

Classify the customer intent first:
@delegate agents/intent-classifier.md

Then search for relevant articles:
@delegate agents/kb-search.md --parallel
Enter fullscreen mode Exit fullscreen mode

The dependency graph looks like this:

SKILL.md
├── shared/role.md
├── shared/product-catalog.md
├── shared/escalation-rules.md
├── shared/response-format.md
├── agents/intent-classifier.md
└── agents/kb-search.md
Enter fullscreen mode Exit fullscreen mode

Clean. Readable. Each file has a single responsibility.


When It Gets Complex: dotmd-parser

Once you have 10+ files referencing each other, questions start coming up:

  • "I want to update shared/escalation-rules.md — what else will break?"
  • "Is there a circular reference somewhere in this skill tree?"
  • "What {{variables}} are left unresolved after expansion?"

This is where dotmd-parser comes in.

pip install dotmd-parser
Enter fullscreen mode Exit fullscreen mode

Build the dependency graph

from dotmd_parser import build_graph, summary

graph = build_graph("./support/")
print(summary(graph))
# Nodes: 7  (skill:1, shared:4, agent:2)
# Edges: 6  (include:4, delegate:2)
# Warnings: 0
Enter fullscreen mode Exit fullscreen mode

Find what breaks before you edit

from dotmd_parser import build_graph, dependents_of

graph = build_graph("./support/")
affected = dependents_of(graph, "/abs/path/to/shared/product-catalog.md")
# → ["/abs/path/to/support/SKILL.md"]
Enter fullscreen mode Exit fullscreen mode

One call tells you the full blast radius of your change.

Detect circular references

graph = build_graph("./bad-skill/")
# Warnings: 1
# [CIRCULAR] 循環参照: bad-skill/SKILL.md -> b.md -> bad-skill/SKILL.md
Enter fullscreen mode Exit fullscreen mode

Circular references are caught at parse time — not when your agent starts looping at runtime.

Expand @include to final text

from dotmd_parser import resolve

result = resolve("./support/SKILL.md", variables={"items": "laptop, mouse"})
print(result["content"])        # Fully expanded prompt
print(result["placeholders"])   # Any unresolved {{variables}}
Enter fullscreen mode Exit fullscreen mode

The @include Convention

There's no official spec for @include in Claude Code — this is a convention we've been building on top of SKILL.md. The format is intentionally simple:

@include relative/path/to/file.md
@delegate relative/path/to/agent.md
@delegate relative/path/to/agent.md --parallel
Enter fullscreen mode Exit fullscreen mode

dotmd-parser also tracks Read \path/to/file.md`references (runtime reads that don't expand inline) and{{variable}}` placeholders.

If you're building anything non-trivial with Claude Code, we'd encourage you to try this pattern. The overhead is low — it's just Markdown files — and the payoff in maintainability is significant.


What's Next: dotmd-io

dotmd-parser is the open-source core. We're building dotmd-io on top of it — a web platform that gives your whole team a visual interface for managing SKILL.md dependencies:

  • Dependency graph visualization (ReactFlow + D3)
  • Monaco Editor with @include/@delegate syntax highlighting
  • A/B testing and prompt scoring via Claude API
  • Git-based version control with one-click rollback
  • RBAC for non-engineers to safely edit prompts

If that sounds useful, watch the repo: github.com/dotmd-projects/dotmd-parser


Getting Started

`bash
pip install dotmd-parser
`

Start with a small skill and try extracting one shared file:

`plaintext
my-skill/
├── SKILL.md # @include shared/role.md
└── shared/
└── role.md # Just the role definition
`

Run the parser to verify:

`bash
dotmd-parser ./my-skill/

Nodes: 2 (skill:1, shared:1)

Edges: 1 (include:1)

Warnings: 0

`

Once it clicks, you'll find yourself refactoring every prompt you write.


Built something with @include? We'd love to see it — open an issue or drop a comment below.


Tags: claudecode promptengineering python ai opensource

Top comments (0)