# Trace Tool Approval Requests in Kimi CLI: From Execution to User Prompt

> Trace tool approval requests in Kimi CLI from execution to user prompt. Understand the flow through approval runtime, wire protocol, and UI with async resolution.

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

---

**Tool approval requests in Kimi CLI flow from the tool layer through an approval runtime, across the wire protocol to the UI, and back to the tool via async resolution.**

The Kimi CLI codebase implements a permission gate for any tool that can modify the system or external resources. Understanding how it handles tool approval requests requires tracing the path from [`src/kimi_cli/soul/approval.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/approval.py) through the runtime and wire layers to the React frontend and back again.

## Stage 1: Tool Execution Creates the Request

When a tool requires user consent, it invokes `Approval.request(...)` in [`src/kimi_cli/soul/approval.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/approval.py). This method first checks the current tool call context using `get_current_tool_call_or_none` to associate the request with the correct call ID.

The method then builds an `ApprovalRequestRecord` defined in [`src/kimi_cli/approval_runtime/models.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/approval_runtime/models.py) and registers it with an `ApprovalRuntime` instance. As implemented in [`src/kimi_cli/approval_runtime/runtime.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/approval_runtime/runtime.py), the `ApprovalRuntime.create_request` method persists the pending request and publishes a `request_created` event. Simultaneously, the request is serialized into an `ApprovalRequest` message and dispatched to the wire layer.

```python

# ---- Inside a tool implementation (e.g., a file‑write tool) ----

async def run(self, path: str, content: str):
    # Request user approval before writing the file

    approval = await self.approval.request(
        sender="file_tool",
        action="write_file",
        description=f"Write {len(content)} bytes to {path}",
        display=[DisplayBlock(type="diff", data=diff_of_content)],
    )
    if not approval:
        raise approval.rejection_error()   # raises ToolRejectedError

    # Approved – perform the write

    with open(path, "w") as f:
        f.write(content)

```

If the session operates in AFK or YOLO mode, `Approval.is_auto_approve()` short-circuits the flow and immediately returns an approved `ApprovalResult` without prompting the user.

## Stage 2: Wire Protocol Delivers the Request to the UI

The wire hub receives the `ApprovalRequest` and forwards it to all UI subscribers. In the React frontend, the `ApprovalDialog` component in [`web/src/features/chat/components/approval-dialog.tsx`](https://github.com/MoonshotAI/kimi-cli/blob/main/web/src/features/chat/components/approval-dialog.tsx) monitors incoming chat messages. It locates the message whose `variant === "tool"` and whose `approval` field indicates an *approval‑requested* state.

From that message, the component extracts the request ID, description, action, and optional display blocks. It then renders a modal with **Approve**, **Approve for session**, **Decline**, and **Decline with feedback** buttons.

```tsx
// ---- React component handling the pending request ----
const handleResponse = useCallback(
  async (decision: ApprovalResponseDecision, reason?: string) => {
    if (pendingApproval && onApprovalResponse) {
      await onApprovalResponse(pendingApproval.approval.id, decision, reason);
    }
  },
  [pendingApproval, onApprovalResponse],
);

```

## Stage 3: User Response Resolves the Pending Request

When the user clicks a button or triggers a keyboard shortcut, `ApprovalDialog` invokes the `onApprovalResponse` callback with the request ID and a decision. The wire layer routes this back to `ApprovalRuntime.resolve(request_id, decision, ...)` in [`src/kimi_cli/approval_runtime/runtime.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/approval_runtime/runtime.py).

The runtime marks the request as *resolved*, records any feedback, and publishes a `request_resolved` event. It then wakes the awaiting coroutine in `Approval.request()` by setting the result on a waiter future. The original tool call receives an `ApprovalResult` object. If approved, execution continues; otherwise, the runtime raises a `ToolRejectedError`.

```python

# ---- Runtime side that resolves the request ----

def resolve(self, request_id: str, decision: ApprovalResponseKind, feedback: str = "", approved_via_session_cache: bool = False):
    request = self._requests[request_id]
    request.status = "resolved"
    request.response = decision
    request.feedback = feedback
    request.approved_via_session_cache = approved_via_session_cache
    self._publish_event(ApprovalRuntimeEvent(kind="request_resolved", request=request))
    # Wake the awaiting coroutine in Approval.request()

    waiter = self._waiters.pop(request_id, None)
    if waiter and not waiter.done():
        waiter.set_result((decision, feedback))

```

If the user selects **Approve for session**, the action name is added to `self._state.auto_approve_actions`. Future tool approval requests with the same action bypass the UI and are auto‑approved. Every outcome branch emits a `permission_approval_result` telemetry event via `_track_permission_result`.

## Summary

- **Entry point:** Tools call `Approval.request()` in [`src/kimi_cli/soul/approval.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/approval.py) to initiate tool approval requests.
- **State management:** `ApprovalRuntime` in [`src/kimi_cli/approval_runtime/runtime.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/approval_runtime/runtime.py) creates, stores, and resolves `ApprovalRequestRecord` instances.
- **Data models:** [`src/kimi_cli/approval_runtime/models.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/approval_runtime/models.py) defines the record types used during the flow.
- **UI rendering:** The `ApprovalDialog` component in [`web/src/features/chat/components/approval-dialog.tsx`](https://github.com/MoonshotAI/kimi-cli/blob/main/web/src/features/chat/components/approval-dialog.tsx) displays pending requests and captures user decisions.
- **Async resolution:** The runtime wakes the blocked tool coroutine with an `ApprovalResult` or raises `ToolRejectedError` when the user declines.
- **Auto-approve modes:** AFK/YOLO sessions and session-wide cached approvals skip the modal entirely.

## Frequently Asked Questions

### How does Kimi CLI associate an approval request with the correct tool call?

The `Approval.request()` method retrieves the active tool call context via `get_current_tool_call_or_none`, which is stored in a `ContextVar`. This ensures each `ApprovalRequestRecord` is linked to the specific call that initiated it.

### What happens when a user chooses "Approve for session"?

The runtime adds the tool action name to `self._state.auto_approve_actions`. Subsequent tool approval requests for that same action are automatically approved without displaying the UI modal.

### Where is the pending request state held while waiting for user input?

Pending requests are stored in the `ApprovalRuntime` instance defined in [`src/kimi_cli/approval_runtime/runtime.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/approval_runtime/runtime.py). The runtime maintains an internal registry of request records and blocked waiter coroutines until `resolve()` is invoked.

### Which message types travel between the runtime and the UI?

The wire layer in [`src/kimi_cli/wire/types.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/wire/types.py) defines `ApprovalRequest` messages for outbound requests and `ApprovalResponse` messages for inbound user decisions. These types enable the bidirectional flow between the Python backend and the React frontend.