# How an Approval Request Is Projected Onto the Wire Stream in Kimi CLI

> Discover how Kimi CLI projects approval requests onto the Wire stream using JSON-RPC messages. Learn how to display permission prompts without blocking agent turns for a seamless UI experience.

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

---

**TLDR:** Kimi CLI projects an approval request onto the Wire stream by emitting a JSON-RPC request message from `WireServer._request_approval`, which lets UI front-ends display permission prompts without blocking the running agent turn.

When a tool running inside the MoonshotAI/kimi-cli repository requires explicit user consent—such as before executing a shell command—the runtime must surface that prompt while keeping the session stream alive. Understanding how an approval request is projected onto the Wire stream reveals the non-blocking architecture that allows multiple concurrent sub-agent approvals across Web UI, TUI, and other front-ends.

## Initiating the Request in `Approval.request`

The permission flow begins in [`src/kimi_cli/soul/approval.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/approval.py). When a tool needs clearance, it invokes **`Approval.request`**, which builds an **`ApprovalRequest`** object and registers it with the **`ApprovalRuntime`** via `self._runtime.create_request`.

A typical tool-side call looks like this:

```python

# src/kimi_cli/soul/approval.py

result = await approval.request(
    sender="shell",
    action="run_command",
    description="Run `rm -rf /tmp`",
    display=display_blocks,
)
if not result:
    raise result.rejection_error()

```

Once the runtime creates and tracks the request, it broadcasts the object onto the session-level hub so the Wire server can intercept it.

## Broadcasting via the `RootWireHub`

The **`RootWireHub`** in [`src/kimi_cli/wire/root_hub.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/wire/root_hub.py) acts as a session-level broadcast bus. The **`WireServer`** listens to this bus through its `_root_hub_loop` in [`src/kimi_cli/wire/server.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/wire/server.py). Whenever the loop dequeues a message that is an instance of `ApprovalRequest`, it immediately forwards it.

```python

# src/kimi_cli/wire/server.py

async def _root_hub_loop(self) -> None:
    while True:
        msg = await self._root_hub_queue.get()
        if isinstance(msg, ApprovalRequest):
            await self._request_approval(msg)   # <-- projects onto Wire

```

This broadcast mechanism ensures that approval requests from background sub-agents reach the same central projection point.

## Projecting the Request Onto the Wire Stream

The projection itself happens inside **`WireServer._request_approval`** in [`src/kimi_cli/wire/server.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/wire/server.py). The method stores the request in `_pending_requests`, uses the request’s own `id` as the JSON-RPC message identifier, and emits a **`JSONRPCRequestMessage`** carrying the `ApprovalRequest` payload:

```python

# src/kimi_cli/wire/server.py

async def _request_approval(self, request: ApprovalRequest) -> None:
    msg_id = request.id                     # use request id as JSON‑RPC id

    self._pending_requests[msg_id] = request
    await self._send_msg(JSONRPCRequestMessage(id=msg_id, params=request))
    # Do NOT await request.wait() – keep the UI loop non‑blocking

```

Because `_request_approval` returns without awaiting the request’s future, the Wire stream remains responsive. UI front-ends receive the JSON-RPC request in real time and can render an approval dialog while the underlying agent turn continues.

## Resolving the Client Response

After the user interacts with the front-end dialog—choosing **approve**, **reject**, or **approve for session**—the client returns an **`ApprovalResponse`**. The `WireServer` handles this in `_handle_response`, correlating the message by its JSON-RPC `id` against `_pending_requests`. It then resolves the original `ApprovalRequest` and notifies the `ApprovalRuntime`.

```python

# src/kimi_cli/wire/server.py

# Inside a structural pattern match on the pending request

case ApprovalRequest():
    if isinstance(msg, JSONRPCErrorResponse):
        request.resolve("reject")
    else:
        result = ApprovalResponse.model_validate(msg.result)
        request.resolve(result.response)
        self._approval_runtime.resolve(request.id, result.response,
                                       feedback=result.feedback)

```

Once resolved, the awaiting tool call in [`approval.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/approval.py) receives the result and either proceeds or raises a rejection error.

## Summary

- **`Approval.request`** in [`src/kimi_cli/soul/approval.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/approval.py) creates an `ApprovalRequest` and registers it with the runtime.
- The **`RootWireHub`** broadcasts the request to the `WireServer._root_hub_loop`.
- **`_request_approval`** projects the request onto the Wire stream as a `JSONRPCRequestMessage` using the request's own `id`.
- The emission is **non-blocking**, so concurrent approvals and streaming can coexist.
- **`_handle_response`** correlates the client's `ApprovalResponse` by JSON-RPC `id`, resolves the pending request, and unlocks the tool.

## Frequently Asked Questions

### What file initiates an approval request in Kimi CLI?

[`src/kimi_cli/soul/approval.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/approval.py) defines the **`Approval.request`** method. Tools call this method to build an `ApprovalRequest` object and register it with the `ApprovalRuntime`.

### How does Kimi CLI keep the UI responsive while waiting for user approval?

The projection method **`_request_approval`** emits the JSON-RPC message but **does not await** `request.wait()`. This non-blocking design keeps the Wire stream alive and allows the UI to process multiple concurrent approval dialogs.

### What message format is used to project approvals onto the Wire stream?

Kimi CLI uses a **`JSONRPCRequestMessage`** defined in the Wire protocol. The message carries the `ApprovalRequest` payload in its `params` field and uses the request's `id` as the JSON-RPC `id` for correlation.

### How is the client's response matched back to the original approval request?

The `WireServer` stores each outgoing request in **`_pending_requests`** keyed by its `id`. When the client replies, `_handle_response` looks up that same `id`, validates the payload as an `ApprovalResponse`, and calls `request.resolve()` to wake the awaiting tool.