Skills Composition and Orchestration
A single Skill solves a single problem, while complex workflows often require multiple Skills to work together.
This article introduces how to design dependencies between Skills and how to orchestrate multi-Skill workflows in SKILL.md.
Composition Patterns: Two Approaches
| Pattern | Description | Applicable Scenario |
|---|---|---|
| Sequential Orchestration | The output of Skill A is used as the input of Skill B | Data processing pipeline |
| Parallel Invocation | Use multiple Skills simultaneously and merge the results | Multi-source data aggregation |
Claude can make cross-Skill calls in a single conversation, but can only "actively read" the body of one Skill at a time. Complex multi-Skill workflows are usually coordinated by an "orchestration Skill."
Sequential Orchestration Example
with"Upload PDF → Extract text → Generate Word summary report"Take this as an example: three Skills collaborate in sequence.
---
name: pdf-to-word-report
description: >
将 PDF 文档转换为 Word 格式的摘要报告,包含文字提取、
内容分析和格式化输出三个阶段。当用户需要从 PDF 生成报告、
提取 PDF 内容并整理为 Word 文档时触发。
---
# PDF 转 Word 摘要报告
## 工作流程
本 Skill 协调三个处理阶段,最终生成 .docx 报告。
### 阶段一:提取 PDF 文字
使用 pdf-reading Skill(位于 /mnt/skills/public/pdf-reading/)的处理逻辑:
```bash
python /mnt/skills/public/pdf-reading/scripts/extract.py \
--input <PDF路径> \
--output /home/claude/extracted_text.txt
```
完成后告知用户:已提取 {N} 页内容,共 {字数} 字。
### 阶段二:生成摘要内容
对提取的文字进行内容分析,生成:
- 文档主题(1句话)
- 关键章节摘要(每章 2-3 句)
- 重要数据与结论
将摘要内容写入 /home/claude/summary.json
### 阶段三:生成 Word 报告
使用 docx Skill 的规范,调用:
```bash
python /mnt/skills/public/docx/scripts/generate.py \
--template /mnt/skills/public/docx/assets/report_template.docx \
--data /home/claude/summary.json \
--output /mnt/user-data/outputs/report.docx
```
完成后调用 present_files 展示下载链接。
Implementing Skill Composition via Scripts
When multiple Skills share scripts, the main script can directly call the scripts of other Skills to form a processing pipeline.
Example
# Orchestrates the processing pipeline of multiple Skill scripts
import subprocess
import sys
import os
import json
SKILLS_BASE = "/mnt/skills/public"
def run_script(script_path: str, args: list) -> dict:
"""Runs the script at the specified path and returns the JSON result"""
cmd = [sys.executable, script_path] + args
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
return {"status": "error", "message": result.stderr}
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return {"status": "success", "raw_output": result.stdout}
def run_pipeline(pdf_path: str, output_dir: str) -> dict:
"""
Execute the complete PDF → Word report generation pipeline
Stage 1: PDF text extraction
Stage 2: Content summary generation (handled by Claude, skipped here)
Stage 3: Word document generation
"""
os.makedirs(output_dir, exist_ok=True)
extracted_txt = os.path.join(output_dir, "extracted.txt")
final_docx = os.path.join(output_dir, "report.docx")
# Stage 1: Extract PDF text
print("Stage 1: Extracting PDF text...")
step1 = run_script(
f"{SKILLS_BASE}/pdf-reading/scripts/extract.py",
["--input", pdf_path, "--output", extracted_txt]
)
if step1.get("status") == "error":
return {"status": "error", "stage": 1, "message": step1["message"]}
print(f" Extracted {step1.get('pages', '?')} pages")
# Stage 3: Generate Word report (the summary is generated and passed in by Claude before this script is called)
summary_json = os.path.join(output_dir, "summary.json")
if not os.path.exists(summary_json):
return {"status": "error", "stage": 3,
"message": "Missing summary.json, please complete Stage 2 first"}
print("Stage 3: Generating Word report...")
step3 = run_script(
f"{SKILLS_BASE}/docx/scripts/generate.py",
["--data", summary_json, "--output", final_docx]
)
if step3.get("status") == "error":
return {"status": "error", "stage": 3, "message": step3["message"]}
return {"status": "success", "output": final_docx}
if __name__ == "__main__":
pdf_path = sys.argv[1] if len(sys.argv) > 1 else ""
output_dir = sys.argv[2] if len(sys.argv) > 2 else "/home/claude/pipeline_output"
result = run_pipeline(pdf_path, output_dir)
print(json.dumps(result, ensure_ascii=False, indent=2))
Avoiding Circular Dependencies
When Skill A depends on Skill B and Skill B depends on Skill A, a circular dependency is formed, making execution impossible.
When designing Skill composition, you should maintain one-way dependency relationships.
Shared Utility Functions
Common functions needed by multiple Skills can be extracted into a shared module to avoid duplicate implementation.
Example
# File utility functions shared by multiple Skills
import os
UPLOAD_DIR = "/mnt/user-data/uploads"
OUTPUT_DIR = "/mnt/user-data/outputs"
def resolve_upload(filename: str) -> str:
"""Parses a filename into the full upload path"""
if os.path.isabs(filename):
return filename # Already an absolute path
return os.path.join(UPLOAD_DIR, filename)
def resolve_output(filename: str) -> str:
"""Parses a filename into the full output path and ensures the directory exists"""
if os.path.isabs(filename):
path = filename
else:
path = os.path.join(OUTPUT_DIR, filename)
os.makedirs(os.path.dirname(path), exist_ok=True)
return path
# Used in each Skill script:
# import sys; sys.path.insert(0, "/home/claude/shared")
# from file_utils import resolve_upload, resolve_output