Chuỗi: cli-to-agent-native · Phần 8
Năng suất & công cụ dev
Agent Skills: giảm 99.6% token cost so với MCP
Static markdown 200-500 tokens mô tả tool cho agent. Block Engineering gọi đây là 'agent skills' — giảm 99.6% token so với MCP với task completion rate tương đương.
2026-03-178 phút đọcVI
- 1.Sự tiến hóa giao diện: GUI → CLI → API → Agent
- 2.Giải phẫu CLI cho agent: 7 nguyên tắc thiết kế
- 3.API trước, CLI sau: Adapter Pattern — tầng trung gian tạo nên CLI chuyên nghiệp
- 4.Từ API spec đến structured metadata: nguồn sự thật cho code generation
- 5.Code Generation Pipeline: Từ metadata JSON đến 16,000 dòng code trong vài giây
- 6.Test generated code: 4 chiến lược cho code mà không ai viết tay
- 7.Biến CLI thành MCP Server: agent tự discover và invoke tool của bạn
- 8.Agent Skills: giảm 99.6% token cost so với MCP(bài này)
- 9.Progressive Enhancement: 5 levels từ raw API đến agent-native — khi nào dùng level nào
- 10.End-to-End Build: Từ 439 API endpoints đến agent-ready CLI — case study: một procurement CLI
English title: Skills: The Lightweight Alternative (99.6% Token Reduction)
Mở đầu
Tháng 2 năm 2026, engineering team tại Block (công ty mẹ của Square, Cash App) publish một bài về "agent skills" — pattern họ dùng để expose internal tools cho AI agents mà không cần MCP server.
Concept đơn giản đến mức gần như tầm thường: một file markdown mô tả tool, workflow, và ví dụ. Agent đọc file này trước khi gọi tool. Không có runtime server, không có SDK, không có protocol.
Token cost: ~200-500 tokens. So với MCP server (~55,000 tokens): giảm 99.6%.
Task completion rate: tương đương hoặc cao hơn.
Đây là bài về tại sao đôi khi giải pháp đơn giản nhất là giải pháp tốt nhất.
1. Skills là gì
Skills doc (thường là SKILL.md) là structured markdown mô tả 3 thứ:
- What the tool does — mô tả ngắn gọn, 1-2 câu
- How to invoke it — exact commands với flag và argument examples
- Common workflows — sequences of commands để accomplish higher-level tasks
Agent đọc Skills doc trước khi bắt đầu task, giống như developer đọc README trước khi dùng thư viện.
# SKILL: User Management CLI
## Purpose
Manage users in the system — list, create, update, delete. All commands return JSON with --json flag.
## Auth
Set API_TOKEN env var. Exit code 4 = auth error.
## Commands
### List users
```bash
myapp list-users --json
myapp list-users --role admin --json # Filter by roleGet user
myapp get-user 42 --json
# Exit code 3 if not foundCreate/ensure user (idempotent)
myapp ensure-user --name "Alice" --email "a@x.com" --json
# Returns: {"action": "created"|"already_exists", "user": {...}}Delete user
myapp delete-user 42 --force --json # --force requiredExit Codes
- 0: Success
- 2: Usage error (missing required flag)
- 3: Not found
- 4: Auth/permission error
- 5: Conflict (already exists)
Common Workflows
Onboard new team member
myapp ensure-user --name "{{name}}" --email "{{email}}" --json
# Extract user.id from JSON output
myapp assign-role --user-id {{id}} --role member --json
myapp send-welcome --user-id {{id}}Audit admin users
myapp list-users --role admin --json | jq '.[] | {id, name, email}'
File này:
- 38 dòng
- ~350 tokens
- Đủ cho agent biết tất cả mọi thứ cần dùng tool
---
## 2. Tại sao Skills hoạt động
Agent (LLM) được pre-trained trên hàng tỷ dòng terminal session, bash scripts, README files. Khi bạn cung cấp Skills doc dạng markdown với command examples, agent "đọc" nó như một developer đọc documentation — không phải parse structured schema.
Đây là lý do:
**MCP schema** nói với agent: "Tool list_users nhận input type: object, properties: role với type: string, enum: ['admin', 'member', 'viewer']..." — ngôn ngữ formal, cần parse.
**Skills doc** nói với agent: "`myapp list-users --role admin --json`" — ngôn ngữ natural, pre-trained pattern.
LLM xử lý natural language tốt hơn formal schema. Skills doc exploit training data của model.
---
## 3. Generate SKILL.md từ metadata pipeline
Skills doc không cần viết tay. Thêm vào codegen pipeline:
```python
# pipeline/generate_skills.py
import json
from pathlib import Path
from datetime import datetime, timezone
def method_to_command_example(method: dict) -> str:
"""Convert metadata method → bash command example."""
cmd = f"myapp {method['cli_command']}"
# Add required params
for param in method.get("parameters", []):
if param.get("required") and param.get("in") != "path":
if param.get("type") == "string":
cmd += f' --{param["name"]} "{{{{ {param["name"]} }}}}"'
else:
cmd += f' --{param["name"]} {{{{ {param["name"]} }}}}'
# Add path params
for param in method.get("parameters", []):
if param.get("in") == "path":
cmd = cmd.replace(method["cli_command"], f'{method["cli_command"]} {{{{ {param["name"]} }}}}')
# Add --force for delete
if method.get("action") == "delete":
cmd += " --force"
cmd += " --json"
return cmd
def generate_workflow_section(methods: list[dict]) -> str:
"""Generate common workflow examples."""
workflows = []
# Create + assign workflow (if both exist)
create_methods = [m for m in methods if m["action"] == "create"]
list_methods = [m for m in methods if m["action"] == "list"]
for create in create_methods[:2]: # Limit to 2 examples
resource = create["resource"]
list_m = next((m for m in list_methods if m["resource"] == resource), None)
if list_m:
workflows.append(f"""### Audit all {resource}s
```bash
{method_to_command_example(list_m)} | jq '.[] | {{id, name}}'
```""")
return "\n\n".join(workflows) if workflows else ""
def generate_skills_doc(metadata_path: str, output_path: str, tool_name: str = "myapp"):
"""Generate SKILL.md từ metadata JSON."""
metadata = json.loads(Path(metadata_path).read_text())
methods = metadata["methods"]
# Group by resource
from collections import defaultdict
by_resource = defaultdict(list)
for m in methods:
by_resource[m["resource"]].append(m)
lines = [
f"# SKILL: {tool_name.title()} CLI",
"",
f"> Auto-generated from API metadata. {len(methods)} commands available.",
f"> Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d')}",
"",
"## Auth",
"Set `API_TOKEN` env var before running commands.",
"",
"## Exit Codes",
"- `0`: Success",
"- `2`: Usage error (missing required argument or flag)",
"- `3`: Resource not found",
"- `4`: Authentication or permission error",
"- `5`: Conflict (resource already exists)",
"",
"## Commands by Resource",
"",
]
for resource, resource_methods in sorted(by_resource.items()):
lines.append(f"### {resource.title()}")
lines.append("")
lines.append("```bash")
for method in sorted(resource_methods, key=lambda m: m["action"]):
example = method_to_command_example(method)
lines.append(f"# {method['summary']}")
lines.append(example)
lines.append("```")
lines.append("")
# Common patterns
lines.extend([
"## Common Patterns",
"",
"### Idempotent create (safe to retry)",
"```bash",
"# Use ensure-* commands — safe to call multiple times",
"# Returns: {\"action\": \"created\"|\"already_exists\", \"object\": {...}}",
"```",
"",
"### Parse JSON output",
"```bash",
f"{tool_name} list-users --json | jq '.[]'",
f"{tool_name} get-user 42 --json | jq '.name'",
"```",
"",
])
# Workflow section
workflow_section = generate_workflow_section(methods)
if workflow_section:
lines.extend([
"## Workflows",
"",
workflow_section,
"",
])
content = "\n".join(lines)
Path(output_path).write_text(content)
token_estimate = len(content.split()) * 1.3 # rough token estimate
print(f"✓ Generated SKILL.md ({len(methods)} commands, ~{token_estimate:.0f} tokens) → {output_path}")
if __name__ == "__main__":
import sys
generate_skills_doc(
metadata_path=sys.argv[1],
output_path=sys.argv[2],
tool_name=sys.argv[3] if len(sys.argv) > 3 else "myapp",
)
python pipeline/generate_skills.py metadata/api-metadata.json SKILL.md myapp
# ✓ Generated SKILL.md (439 commands, ~487 tokens) → SKILL.md4. Cách agent sử dụng Skills doc
Có hai cách inject Skills doc vào agent context:
Cách 1: System prompt — Append SKILL.md vào system prompt của agent
# agent.py
from anthropic import Anthropic
from pathlib import Path
client = Anthropic()
skill_doc = Path("SKILL.md").read_text()
response = client.messages.create(
model="claude-opus-4-6",
system=f"""You are an assistant that manages users via CLI.
{skill_doc}
When the user asks you to manage users, use the commands above via subprocess.
Always use --json flag and parse the output.""",
messages=[{"role": "user", "content": "List all admin users"}]
)Cách 2: On-demand loading — Agent load Skills doc khi cần (lazy)
# Agent đọc SKILL.md như một tool
tools = [
{
"name": "read_skill_doc",
"description": "Read the CLI tool documentation before using any commands",
"input_schema": {
"type": "object",
"properties": {"tool": {"type": "string"}},
"required": ["tool"]
}
},
{
"name": "run_cli",
"description": "Execute a CLI command",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "Full command to execute"}
},
"required": ["command"]
}
}
]
# Tool handler
def handle_tool(name: str, input: dict) -> str:
if name == "read_skill_doc":
return Path(f"{input['tool']}-SKILL.md").read_text()
if name == "run_cli":
import subprocess
result = subprocess.run(input["command"].split(), capture_output=True, text=True)
return result.stdout or result.stderrCách 2 là zero upfront cost — agent chỉ load Skills doc khi nó quyết định cần. Đây là pattern mà Block Engineering describe là "on-demand skill loading".
5. Skills doc vs MCP: decision framework
Dùng Skills doc khi:
├── Tool < 20 commands → Skills doc đủ describe trong 200-500 tokens
├── Agent workflow đơn giản → 1-3 CLI calls per task
├── Context window là bottleneck → không thể afford 55k token overhead
├── Static tool (không thay đổi thường xuyên) → update Skills doc dễ
└── Subprocess OK → không cần stateful server
Dùng MCP khi:
├── Tool > 50 commands → agent cần dynamic discovery
├── Interactive session → schema load once, reuse nhiều lần
├── Stateful operations → database connections, sessions, caching
├── IDE integration → Claude Code, Cursor cần MCP format
└── Multiple agents share tools → central registry có nghĩa
6. Ba nguyên tắc của Block Engineering
Block Engineering publish 3 nguyên tắc cho agent skills:
Nguyên tắc 1: Narrow scope. Mỗi skill mô tả một domain, không phải toàn bộ platform. user-management-SKILL.md, không phải everything-SKILL.md. Agent load skill khi relevant, không phải luôn luôn.
Nguyên tắc 2: Show, don't tell. Command examples > abstract description. myapp ensure-user --name "Alice" --email "a@x.com" --json tốt hơn "creates user if not exists". Agent đã được trained trên command patterns — example trực tiếp trigger correct behavior.
Nguyên tắc 3: Exit codes over error messages. Skills doc phải document exit codes. Agent branch logic dựa trên exit codes, không phải parse error messages. Đây là contract giữa tool và agent.
7. Ứng dụng trong AI-centric engineering
Skills pattern đặc biệt phù hợp với internal tools trong enterprise context:
- Internal tools ít commands (10-50) — Skills doc đủ
- Internal APIs thay đổi ít — update Skills doc khi cần
- Agent workflows bounded — không cần dynamic discovery
- Context efficiency quan trọng — enterprise LLM calls tốn tiền
Với 487 tokens cho SKILL.md thay vì 55,000 tokens cho MCP schema, bạn có thể inject 110 Skills docs vào cùng context window mà MCP dùng để load một server.
Đó là multiplier thực sự — không phải về một tool, mà về bao nhiêu tools agent có thể aware of cùng lúc.
Bài tiếp
Bài 9: Progressive Enhancement Ladder — CLI → MCP → Skills → Agent SDK — Khi nào dùng level nào. Data benchmark: CLI 28% cao hơn MCP với token tương đương. Framework quyết định: 5 câu hỏi để chọn đúng level cho từng use case. Template để apply vào project của bạn.
