# How Kimi Toolset Loads and Executes Tools Imported by Their Path

> Discover how Kimi Toolset loads and executes tools via path. Explore dynamic imports, dependency injection, and asynchronous execution for efficient tool management.

- Repository: [Moonshot AI/kimi-cli](https://github.com/MoonshotAI/kimi-cli)
- Tags: internals
- Published: 2026-07-22

---

**Kimi Toolset dynamically imports tool classes using `importlib.import_module`, instantiates them with automatic dependency injection, and executes calls asynchronously through a centralized `handle` method that manages context, deduplicates requests, and integrates lifecycle hooks.**

The `KimiToolset` class in the MoonshotAI/kimi-cli repository serves as the central registry that makes built-in, MCP, and external tools available to the LLM runtime. It bridges static tool specifications with live Python objects by resolving import paths, managing runtime dependencies, and orchestrating execution with comprehensive error handling, deduplication, and telemetry tracking.

## Tool Discovery and Dynamic Loading via Import Paths

Tools in Kimi CLI are identified using an import path string following the format `module_path:ClassName` (e.g., `kimi_cli.tools.shell:Shell`). These paths originate from agent specification YAML files or direct invocations of the loading API.

### The `load_tools` Entry Point

In [`src/kimi_cli/soul/toolset.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/toolset.py), the `load_tools` method (lines 83-108) iterates over provided path strings and delegates to the private `_load_tool` helper for each entry. This method accepts a `dependencies` dictionary containing runtime objects like loggers and configuration that may be injected into tool instances.

### Dynamic Import Resolution

The `_load_tool` implementation splits the import path string on the colon delimiter, then uses `importlib.import_module` to load the specified module. It retrieves the class object via `getattr`. If the class cannot be imported or is missing from the module, the toolset logs a warning and skips the entry rather than failing the entire loading process.

### Dependency Injection During Instantiation

Once the class is retrieved, the toolset inspects its `__init__` signature using `inspect.signature(tool_cls)` (lines 31-41). Positional parameters are automatically populated with objects from the `dependencies` mapping passed to `load_tools`. Keyword-only parameters halt this injection process, allowing the tool to receive user-supplied arguments at call time. Successfully instantiated tools are registered in the internal `_tool_dict` dictionary via `self.add(tool)`, using the tool's `name` property as the lookup key.

## Executing Tool Calls in the Kimi Runtime

When the LLM emits a tool call, the runtime dispatches to `KimiToolset.handle`, which implements a comprehensive execution pipeline.

### Context Management and Validation

The `handle` method first stores the current `ToolCall` in a `ContextVar` so nested code can access it via `get_current_tool_call_or_none` (lines 44-49). It then validates the tool name against `_tool_dict`, raising a `ToolNotFoundError` if the tool is not registered (lines 48-53).

### Argument Parsing and Deduplication

The JSON arguments from the LLM are parsed using `json.loads`, with parsing errors wrapped in a `ToolParseError` (lines 54-63). Before execution, the system builds a canonical representation of the arguments via `_canonical_tool_arguments`. A call key constructed from `(tool_name, canonical_args)` enables detection of same-step or cross-step duplicates, preventing redundant executions and optionally triggering reminder text insertion (lines 65-84).

### Hook Integration and Async Execution

The `HookEngine` fires a `PreToolUse` event before execution (lines 58-73). If any hook returns `action="block"`, execution stops immediately with a `ToolError`. Otherwise, the tool's `call` coroutine is awaited: `await tool.call(arguments)` (lines 84-110). Exceptions during execution are caught and wrapped in a `ToolRuntimeError`, while successful results are encapsulated in a `ToolResult` object.

### Telemetry and Cleanup

Execution metrics, outcomes, and errors are sent to the telemetry system, followed by a fire-and-forget `PostToolUse` hook (lines 110-130). If the call was identified as a duplicate, the return value is augmented with a reminder message via `_append_reminder_to_return_value` (lines 94-108). Finally, the `ContextVar` is reset to its previous state, and the resulting `asyncio.Task[ToolResult]` is returned to the caller (lines 129-134).

## Practical Implementation Examples

The following examples demonstrate how to load tools and handle incoming calls from the LLM runtime:

```python

# Loading built-in tools with dependency injection

from kimi_cli.soul.toolset import KimiToolset

toolset = KimiToolset()
toolset.load_tools(
    tool_paths=["kimi_cli.tools.shell:Shell", "kimi_cli.tools.file:File"],
    dependencies={"logger": logger, "config": config},
)

```

```python

# Handling an incoming tool call from the LLM

from kimi_cli.wire.types import ToolCall, ToolCallFunction

incoming = ToolCall(
    id="call-123",
    function=ToolCallFunction(name="shell", arguments='{"cmd":"ls -l"}')
)

# handle() returns an asyncio.Task[ToolResult]

result_task = toolset.handle(incoming)
result = await result_task
print(result.return_value)  # ToolOk with command output

```

```python

# Registering an external (wire) tool at runtime

ok, err = toolset.register_external_tool(
    name="my_external",
    description="Calls a remote service",
    parameters={
        "type": "object",
        "properties": {"url": {"type": "string"}}
    },
)
assert ok, err

```

## Summary

- Kimi Toolset resolves tool specifications using `module_path:ClassName` strings processed by `importlib.import_module` in [`src/kimi_cli/soul/toolset.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/toolset.py).
- The `load_tools` method orchestrates discovery and instantiates tools with automatic dependency injection based on `inspect.signature` analysis.
- Tool execution occurs through the async `handle` method, which sets up request context via `ContextVar`, validates tool existence against `_tool_dict`, and parses JSON arguments.
- Built-in deduplication logic using `_canonical_tool_arguments` prevents redundant executions by tracking unique `(tool_name, arguments)` pairs across steps.
- The execution pipeline integrates `PreToolUse` and `PostToolUse` hooks via the `HookEngine`, with comprehensive telemetry collection and error wrapping in `ToolRuntimeError`.

## Frequently Asked Questions

### What file format does Kimi Toolset use to specify tool import paths?

Kimi Toolset uses a colon-separated string format of `module_path:ClassName` (e.g., `kimi_cli.tools.shell:Shell`). These strings are typically defined in agent specification YAML files under the `tools:` section or passed directly to the `load_tools` method. The system splits this string to dynamically import the module using `importlib.import_module` and retrieve the class via `getattr`.

### How does Kimi Toolset inject dependencies into tool instances during loading?

During instantiation in `_load_tool`, the toolset inspects the tool class's `__init__` signature using `inspect.signature` (lines 31-41). It automatically maps positional parameters to objects provided in the `dependencies` dictionary (such as loggers or configuration). Keyword-only parameters in the signature stop this injection, ensuring they receive runtime arguments from the actual tool call rather than dependencies.

### What happens when a tool execution fails or the requested tool is not found?

If the tool name is not found in `_tool_dict`, `KimiToolset.handle` returns a `ToolNotFoundError` immediately (lines 48-53). If JSON argument parsing fails, it returns a `ToolParseError`. During execution, any unhandled exceptions from `await tool.call()` are caught and wrapped in a `ToolRuntimeError`. These error types are part of the `ToolResult` structure returned to the LLM runtime, allowing the system to report specific failure modes to the agent.

### How does the toolset prevent duplicate tool executions within the same conversation?

The `handle` method computes a canonical representation of the tool arguments using `_canonical_tool_arguments` (lines 65-84) and constructs a unique call key combining the tool name and these canonical args. If the same key appears within a step or across recent steps, the system identifies it as a duplicate, prevents redundant execution, and optionally appends a reminder message to the return value via `_append_reminder_to_return_value` to inform the LLM of the repetition.