0Pricing
Claude Architect · Lesson

Anatomy of a Great Description

Purpose, returns, input formats, edge cases, boundaries.

Anatomy of a Great Description is a free Claude Architect lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Claude Architect learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Descriptions Do the Routing

When Claude decides which tool to call, it reads the tool descriptions, not the names. The description is the primary selection mechanism. A clever name with a vague description loses every time.

So a great description is not documentation you write for humans. It is the routing signal the model uses to pick the right action under pressure.

In this lesson we dissect the anatomy of that signal: purpose, return values, input formats, edge cases, and applicability boundaries.

The Five Anatomy Parts

A description that reliably routes work contains five parts:

  • Purpose — what the tool does and when to reach for it
  • Return values — what comes back, so the model can plan its next step
  • Input formats — field shapes, with concrete examples
  • Edge cases — empty results, ambiguity, failures
  • Applicability boundaries — when NOT to use it

Drop any one of these and selection reliability drops. Overlapping or ambiguous descriptions cause misrouting between similar tools.

Part 1 — Purpose

Start with a crisp statement of purpose: the action plus the trigger condition. Compare a weak description to a strong one.

The weak one ("Order tool") forces the model to guess. The strong one tells the model exactly when this tool is the right call versus a neighbor.

lookup_order = {
    "name": "lookup_order",
    # WEAK: "Order tool."
    # STRONG:
    "description": (
        "Retrieve the status, line items, and ship date of a "
        "single order. Use this AFTER get_customer has returned a "
        "verified customer_id, when the user asks about an existing "
        "purchase. Do NOT use to issue refunds (see process_refund)."
    ),
    "input_schema": {
        "type": "object",
        "properties": {"order_id": {"type": "string"}},
        "required": ["order_id"],
    },
}

Part 2 — Return Values

The model plans the next step in the agentic loop from what your tool returns. If the description hides the shape of the result, the model can't chain tools intelligently.

State what comes back and in what form. Mention the fields the model will likely act on next, such as an order_id it must pass to a later tool.

get_customer = {
    "name": "get_customer",
    "description": (
        "Look up a customer by email or phone. RETURNS a JSON object "
        "with customer_id (string), verified (bool), and recent_order_ids "
        "(array). Pass the returned customer_id to lookup_order or "
        "process_refund. If verified is false, do not process a refund."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "email": {"type": "string"},
            "phone": {"type": "string"},
        },
    },
}

Part 3 — Input Formats With Examples

Ambiguous input formats cause silent failures. Spell out the expected shape of each field and give a concrete example value. Examples remove guesswork far better than prose alone.

Show the date format, the ID pattern, the units. The model generalizes from a single clear example.

process_refund = {
    "name": "process_refund",
    "description": (
        "Issue a refund for a verified order. "
        "order_id: the order string from lookup_order, e.g. 'ORD-4821'. "
        "amount_usd: a positive number in US dollars, e.g. 29.99. "
        "reason: short free text, e.g. 'damaged on arrival'. "
        "RETURNS a refund_id and status."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "order_id": {"type": "string"},
            "amount_usd": {"type": "number"},
            "reason": {"type": "string"},
        },
        "required": ["order_id", "amount_usd", "reason"],
    },
}

Part 4 — Edge Cases

Tell the model how the tool behaves at the edges, so it can recover instead of stalling. Three edges matter most:

  • Empty result — a valid "no matches", which is different from a failure
  • Ambiguity — multiple customers match; the model should ask for more identifiers, never guess
  • Failure — distinguish an access failure (maybe retry) from a clean empty result

Naming these in the description turns dead ends into next moves.

find_customer = {
    "name": "find_customer",
    "description": (
        "Search customers by name. EDGE CASES: returns an empty array "
        "when no one matches (a valid result, not an error). Returns "
        "MULTIPLE matches for common names — when that happens, ask the "
        "user for an email or order id to disambiguate; never guess. "
        "On a backend access failure returns isError:true so you can retry."
    ),
    "input_schema": {
        "type": "object",
        "properties": {"name": {"type": "string"}},
        "required": ["name"],
    },
}

Part 5 — Applicability Boundaries

The fastest way to stop misrouting between two similar tools is to write the boundary into both descriptions. Say explicitly when NOT to use each one and point at its neighbor.

This is essential when descriptions overlap. Without an explicit boundary, the model picks one almost at random and the wrong action fires.

# Two neighbors that would otherwise collide:
search_docs = {
    "name": "search_docs",
    "description": (
        "Full-text search over PUBLISHED help-center articles. "
        "Use for general how-to and policy questions. "
        "Do NOT use for a specific customer's order data — use lookup_order."
    ),
}

lookup_order = {
    "name": "lookup_order",
    "description": (
        "Fetch ONE customer's order by id. "
        "Do NOT use for general policy questions — use search_docs."
    ),
}

Boundaries Enable Least Privilege

Sharp boundaries also help you scope tools to a role. The optimal load is about 4 to 5 tools per agent; once you pass roughly 18 tools, selection reliability degrades badly.

If a tool's boundary says "this is for billing, not support", that's a signal it belongs to a different agent entirely. Give each subagent the least-privilege set it actually needs.

billing_agent = {
    "name": "billing_agent",
    "description": "Handles invoices, refunds, and payment disputes only.",
    "allowed_tools": [
        "get_customer",
        "lookup_order",
        "process_refund",
        "escalate_to_human",
    ],  # 4 scoped tools — not 18
}

Descriptions vs. Hooks

A description guides behavior, but it is still probabilistic (~90% reliable). It cannot guarantee a policy. When a rule has financial, legal, or safety consequences, enforce it deterministically.

Write the intent in the description AND back it with a hook. A PostToolUse or outgoing-call hook can block a refund over $500 with 100% determinism, no matter how the model reasoned.

Describe to guide; hook to guarantee.

# Description sets intent; the hook enforces it deterministically.
def pre_refund_hook(tool_name, tool_input):
    if tool_name == "process_refund" and tool_input["amount_usd"] > 500:
        return {
            "block": True,
            "reason": "Refund > $500 requires human approval.",
        }
    return {"block": False}

Structured Errors Beat Prose

A great description promises useful failures. "Operation failed" blocks recovery; a structured error enables intelligent routing.

Design your tool to return isError:true plus an errorCategory (transient / validation / business / permission), an isRetryable flag, the attempted_query, and any partial_results. Then say so in the description so the model knows it can trust and act on those fields.

{
  "isError": true,
  "errorCategory": "transient",
  "isRetryable": true,
  "message": "Order DB timed out",
  "attempted_query": "lookup_order(order_id='ORD-4821')",
  "partial_results": []
}

A Review Checklist

Before you ship a tool, run its description through this checklist:

  • Purpose — action plus a clear trigger condition?
  • Returns — fields the model needs for the next step?
  • Inputs — formats with at least one concrete example?
  • Edge cases — empty, ambiguous, and failure behavior named?
  • Boundaries — explicit "do NOT use for…", pointing to the neighbor tool?

If two tools could plausibly answer the same request, their boundaries are not sharp enough. Rewrite until only one is the obvious choice.

Quick Check

An architect reviews two tools whose descriptions are short and overlap. The agent keeps calling the wrong one. What is the most effective fix?

Recap

Tool descriptions are routing signals, and the description — not the name — is what the model selects on.

A great one has five parts: purpose (action + trigger), return values (for the next step), input formats (with examples), edge cases (empty vs. ambiguous vs. failure), and applicability boundaries (when NOT to use it).

Keep agents to ~4–5 scoped tools. Use descriptions to guide, but enforce financial, legal, or safety rules with deterministic hooks. And design tools to return structured errors so failures route intelligently. Write the boundary until only one tool is ever the obvious choice.

Frequently asked questions

Is the “Anatomy of a Great Description” lesson free?

Yes — the full text of “Anatomy of a Great Description” is free to read here on the web, and the Claude Architect course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Claude Architect course, upgrade to CoddyKit PRO.

What will I learn in “Anatomy of a Great Description”?

Purpose, returns, input formats, edge cases, boundaries. You practise Claude Architect with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Claude Architect?

No prior experience is required. Claude Architect on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Anatomy of a Great Description” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Claude Architect lesson?

Yes. Every Claude Architect lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Tool Descriptions Drive Selection
  2. Anatomy of a Great Description
  3. Avoiding Overlapping Tools
  4. Input Formats & Examples
← Back to Claude Architect