Lê Duy Khương (Daniel)

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

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ứ:

  1. What the tool does — mô tả ngắn gọn, 1-2 câu
  2. How to invoke it — exact commands với flag và argument examples
  3. 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 role

Get user

myapp get-user 42 --json
# Exit code 3 if not found

Create/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 required

Exit 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.md

4. 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.stderr

Cá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.

LDK

Le Duy Khuong

AI Transformation & Digital Strategy. Writing about agentic systems, engineering leadership, and building in public.