Skip to content

52 medium command tree discovery

Part VII: Ecosystem, Runtime & Agent-Specific | Challenge §52

52. Recursive Command Tree Discovery Cost

Severity: Medium | Frequency: Very Common | Detectability: Easy | Token Spend: High | Time: Medium | Context: High

The Problem

Most CLIs require N+1 help calls to discover the full command surface: one call to list top-level subcommands, then one more call per subcommand to see its flags. A tool with 20 subcommands requires 21 sequential round trips just to build a mental model before any real work begins. With 3+ levels of nesting (tool resource type action --flags), the discovery cost grows exponentially. There is no standard for returning the full command tree in a single structured call.

$ tool --help
# Shows: create, list, update, delete, config, auth, export  ← 7 subcommands

$ tool create --help     # call 2
$ tool list --help       # call 3
$ tool update --help     # call 4
$ tool delete --help     # call 5
$ tool config --help     # call 6
$ tool config get --help # call 7 — subcommand of subcommand
$ tool config set --help # call 8
# ... 8+ calls just to see all flags, before any work starts

# Deep nesting:
$ aws ec2 describe-instances --help    # 3 levels deep
# Discovering the full aws CLI surface: hundreds of calls

The problem compounds when the agent must select the right command for a task — it needs to see all available commands before choosing, which means paying the full discovery cost upfront.

Impact

  • 10–50+ tool calls burned on discovery before first productive call
  • Full command tree may consume a large fraction of the context window when retrieved incrementally
  • Agent may miss relevant subcommands it didn't know to ask about
  • Cached command trees go stale after tool updates; no standard way to detect staleness

Solutions

Single-call full tree export:

$ tool --schema --full
{
  "tool": "my-tool",
  "version": "1.2.3",
  "schema_version": "1.0",
  "commands": [
    {
      "name": "create",
      "description": "Create a new resource",
      "args": [],
      "flags": [],
      "subcommands": []
    },
    {
      "name": "config",
      "subcommands": [
        { "name": "get", "args": [] },
        { "name": "set", "args": [] }
      ]
    }
  ]
}

For framework design: - tool --schema (REQ-O-013) MUST return the full command tree by default, not just the top-level command - Each command node in the tree includes: name, description, args with types and constraints, flags, required/optional status, subcommands - The full schema export must be a single synchronous call completing in under 500ms regardless of command count

Evaluation

Score Condition
0 No single-call schema export; discovering all subcommands requires N+1 sequential help calls
1 tool --schema returns top-level commands only; subcommand details require additional help calls
2 tool --schema --output json returns full tree in one call; each command includes name, description, flags
3 Full tree export including args with types, required/optional status, and nested subcommands; completes in <500ms

Check: Run tool --schema --output json and verify the output is a complete tree with all subcommands and their flags — no additional --help calls needed.


Agent Workaround

Load the full schema tree in one call at session start; cache it for the session:

import subprocess, json

_schema_cache: dict = {}

def get_schema(tool: str) -> dict:
    if tool in _schema_cache:
        return _schema_cache[tool]

    # Try single-call full tree first
    result = subprocess.run(
        [tool, "--schema", "--output", "json"],
        capture_output=True, text=True,
        timeout=10,
    )
    try:
        schema = json.loads(result.stdout)
        _schema_cache[tool] = schema
        return schema
    except json.JSONDecodeError:
        pass

    # Fall back: collect top-level commands from --help
    result = subprocess.run([tool, "--help"], capture_output=True, text=True)
    import re
    commands = re.findall(r'^\s{2,4}(\w[\w-]*)\s', result.stdout, re.MULTILINE)
    schema = {"commands": [{"name": cmd} for cmd in commands]}
    _schema_cache[tool] = schema
    return schema

def find_command(schema: dict, cmd_name: str) -> dict | None:
    for cmd in schema.get("commands", []):
        if cmd.get("name") == cmd_name:
            return cmd
        sub = find_command({"commands": cmd.get("subcommands", [])}, cmd_name)
        if sub:
            return sub
    return None

Limitation: If the tool has no --schema flag and produces only human-formatted help, the agent must make N+1 sequential help calls to discover all subcommands — cache results aggressively and accept that the discovery budget is spent once per session