# How Lazy Command Loading Works in agents-cli's Click-Based Architecture

> Discover how lazy command loading in agents-cli defers heavy module imports until command invocation optimizing performance through its Click-based architecture.

- Repository: [Google/agents-cli](https://github.com/google/agents-cli)
- Tags: internals
- Published: 2026-07-01

---

**The agents-cli tool implements lazy command loading through a custom `LazyGroup` class that stores import paths and help strings during registration, deferring heavy module imports until a user actually invokes a specific command.**

This optimization pattern appears in the `google/agents-cli` repository to keep CLI startup times fast while maintaining a rich command structure. By subclassing Click's standard `Group` class, the tool avoids paying the import cost for unused subcommands like scaffolding, evaluation pipelines, or publishing workflows.

## The LazyGroup Implementation

The core mechanism lives in [`src/google/agents/cli/_click.py`](https://github.com/google/agents-cli/blob/main/src/google/agents/cli/_click.py), where the `LazyGroup` class extends `click.Group` with deferred loading capabilities.

### Storing Command References Without Importing

Instead of importing command modules at startup, `LazyGroup` maintains a registry of import metadata:

```python
class LazyGroup(click.Group):
    """Click group that defers importing subcommand modules until needed."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._lazy_commands: dict[str, tuple[str, str]] = {}

    def add_lazy_command(self, name: str, import_path: str, short_help: str) -> None:
        # Store the "name → (module_path:attr, short_help)" mapping.

        self._lazy_commands[name] = (import_path, short_help)

```

The `add_lazy_command` method populates `_lazy_commands` with a mapping of command names to tuples containing the import path and short help text. For example, the string `"google.agents.cli.setup.cmd_setup:cmd_setup"` tells Python exactly which module and attribute to retrieve later.

### Listing Commands Without Triggering Imports

To ensure `agents-cli --help` remains responsive, the class overrides `list_commands` to merge eagerly-loaded commands with lazy ones:

```python
def list_commands(self, ctx):
    return sorted(set(super().list_commands(ctx)) |
                  set(self._lazy_commands))

```

This allows the help system to display all available command names immediately, without importing the underlying implementation modules.

## On-Demand Command Resolution

When a user executes `agents-cli <command>` or requests `<command> --help`, Click queries the group for the actual command object.

### The get_command Method

The `get_command` method in `LazyGroup` performs the actual import only when needed:

```python
def get_command(self, ctx, cmd_name):
    if cmd_name in self._lazy_commands and cmd_name not in self.commands:
        import_path, _ = self._lazy_commands[cmd_name]
        module_path, attr = import_path.split(":")
        cmd = getattr(importlib.import_module(module_path), attr)
        patch_source_in_help(cmd)          # adds source-path footer

        self.commands[cmd_name] = cmd
    return super().get_command(ctx, cmd_name)

```

The method parses the stored `"module_path:attr"` string, dynamically imports the module using `importlib.import_module`, retrieves the Click command object via `getattr`, and caches it in `self.commands`.

### Caching for Subsequent Calls

Once loaded, commands reside in the standard `self.commands` dictionary. Subsequent invocations of the same command bypass the import logic entirely, providing fast repeated execution while maintaining the initial startup performance benefits.

## Help Output Optimization

Displaying accurate help text without triggering imports requires special handling for lazy entries.

### The format_commands Override

The `format_commands` method distinguishes between loaded and lazy commands when building help output:

```python
def format_commands(self, ctx, formatter):
    rows = []
    for name in self.list_commands(ctx):
        if name in self.commands:
            rows.append((name, self.commands[name].get_short_help_str(limit=1000)))
        else:
            lazy = self._lazy_commands.get(name)
            if lazy is not None:
                rows.append((name, lazy[1]))   # short_help from registration

    if rows:
        with formatter.section("Commands"):
            formatter.write_dl(rows)

```

For lazy commands, the method uses the `short_help` string provided during registration rather than accessing the command object's docstring, ensuring zero-import help generation.

## Source Path Attribution

A diagnostic feature adds the source file path to every command's help output.

### The patch_source_in_help Function

The `patch_source_in_help` function decorates commands to append a "Source:" line indicating the absolute file path where the command is defined. This runs exactly once per command thanks to an internal `_source_patched` guard, executing only during the first lazy load.

## Registration in the Root CLI

The root CLI group in [`src/google/agents/cli/main.py`](https://github.com/google/agents-cli/blob/main/src/google/agents/cli/main.py) subclasses `LazyGroup` as `_MainGroup` and registers every top-level command using the lazy pattern:

```python
@cli.group(cls=_MainGroup)
def main():
    """Google Agents CLI tool."""
    pass

main.add_lazy_command("setup", "google.agents.cli.setup.cmd_setup:cmd_setup", 
                     "Install agents-cli and skills")
main.add_lazy_command("scaffold", "google.agents.cli.scaffold.cmd_scaffold_group:scaffold_group", 
                     "Scaffold new agents and skills")
main.add_lazy_command("publish", "google.agents.cli.publish.cmd_publish_group:publish_group", 
                     "Publish agents to the marketplace")

```

Heavy modules like `scaffold` templates or `publish` workflows remain unimported until explicitly invoked.

## Benefits of Lazy Loading

This architecture provides several concrete advantages:

- **Fast startup time** – Only [`main.py`](https://github.com/google/agents-cli/blob/main/main.py) and [`_click.py`](https://github.com/google/agents-cli/blob/main/_click.py) load initially; heavy dependencies like rendering engines or API clients stay unloaded until needed.
- **Reduced memory footprint** – Unused subcommands never occupy RAM, keeping the base process lightweight.
- **Accurate help display** – Short help strings defined at registration time ensure `agents-cli --help` shows complete command listings without importing implementation details.
- **Source traceability** – Every command help output includes the absolute source path, helping developers locate implementation files quickly.

## Summary

- **LazyGroup** in [`src/google/agents/cli/_click.py`](https://github.com/google/agents-cli/blob/main/src/google/agents/cli/_click.py) stores command metadata (import path and help text) instead of command objects.
- **add_lazy_command** registers commands without importing their modules, using a `"module_path:attribute"` string format.
- **get_command** performs the actual import via `importlib` only when a command is invoked, caching the result for future calls.
- **format_commands** displays help using pre-registered short descriptions, avoiding imports during help generation.
- **patch_source_in_help** adds source file attribution to commands exactly once during their first load.
- The root CLI in [`src/google/agents/cli/main.py`](https://github.com/google/agents-cli/blob/main/src/google/agents/cli/main.py) uses this pattern for all subcommands, ensuring fast startup while supporting a rich command hierarchy.

## Frequently Asked Questions

### How does agents-cli avoid slow startup times with many subcommands?

The tool avoids slow startup by using the `LazyGroup` class to defer module imports. When you run `agents-cli --help`, the system only imports [`main.py`](https://github.com/google/agents-cli/blob/main/main.py) and [`_click.py`](https://github.com/google/agents-cli/blob/main/_click.py), displaying command names from the `_lazy_commands` dictionary without loading heavy implementation modules like scaffolders or publishers.

### What is the string format used for lazy command registration?

The registration uses a `"module_path:attribute"` format, such as `"google.agents.cli.setup.cmd_setup:cmd_setup"`. This string tells Python which module to import and which specific Click command object to retrieve from that module's namespace.

### Does lazy loading affect command execution performance after the first run?

No. The first invocation of a command triggers the import and executes `patch_source_in_help`, but the resulting command object is cached in `self.commands`. Subsequent calls to the same command bypass the import logic and execute at native speed.

### Can nested command groups also use lazy loading?

Yes. Nested groups like `scaffold_group` can themselves subclass `LazyGroup` and call `add_lazy_command` for their own subcommands. This creates a hierarchical lazy-loading system where even complex nested commands remain unloaded until the specific subcommand path is invoked.