# How Kimi CLI Handles DMail (Delayed Mail) for Checkpointed Replies

> Learn how Kimi CLI processes DMail delayed mail for checkpointed replies. Explore the DenwaRenji manager that validates and stores pending DMail for later attachment to checkpoints.

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

---

**Kimi CLI implements delayed mail through a `DenwaRenji` manager that validates, stores, and later attaches a single pending `DMail` to a specific checkpoint after the current tool execution cycle completes.**

Kimi CLI supports **DMail** (delayed mail) to enable AI agents to send messages that retroactively attach to previously created checkpoints. According to the MoonshotAI/kimi-cli source code, this mechanism centers on a validation manager and a tool interface that decouples message recording from UI attachment, ensuring deterministic ordering even when multiple tool calls execute in a single turn.

## Core Architecture

The DMail system relies on three coordinated components defined in [`src/kimi_cli/soul/denwarenji.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/denwarenji.py) and [`src/kimi_cli/tools/dmail/__init__.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/tools/dmail/__init__.py):

### DMail Model

The **`DMail`** Pydantic schema (lines 6-10 in [`denwarenji.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/denwarenji.py)) defines the payload structure for delayed messages. It requires two fields:

- `message`: The text content to attach to the checkpoint
- `checkpoint_id`: The integer index of the target checkpoint

```python
from kimi_cli.soul.denwarenji import DMail

mail = DMail(
    message="Analysis complete. See attached files.",
    checkpoint_id=2
)

```

### DenwaRenji Manager

The **`DenwaRenji`** class (lines 15-40 in [`denwarenji.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/denwarenji.py)) acts as the central gatekeeper. It maintains:

- `_pending_dmail`: Storage for a single in-flight DMail
- `_n_checkpoints`: The current checkpoint count for validation
- `set_n_checkpoints(n)`: Method called by the soul to update checkpoint awareness
- `send_dmail(dmail)`: Validation and storage method
- `fetch_pending_dmail()`: Retrieval method called by the main loop

```python
from kimi_cli.soul.denwarenji import DenwaRenji

renji = DenwaRenji()
renji.set_n_checkpoints(5)  # Inform manager of available checkpoints

```

### SendDMail Tool

The **`SendDMail`** tool class exposes DMail capability to LLM-generated tool calls. Located in [`src/kimi_cli/tools/dmail/__init__.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/tools/dmail/__init__.py), this tool receives the `DMail` payload and forwards it to the `DenwaRenji` manager without directly modifying UI state.

```python
from kimi_cli.tools.dmail import SendDMail

send_tool = SendDMail(renji)
await send_tool(mail)  # May raise DenwaRenjiError on validation failure

```

## Execution Flow

Kimi CLI processes delayed mail through four distinct phases:

1. **Checkpoint Registration** – When the soul creates checkpoints (e.g., after tool execution), it calls `DenwaRenji.set_n_checkpoints(n)` to synchronize the manager with the current checkpoint count.

2. **LLM Tool Invocation** – The LLM requests `SendDMail` with a JSON payload matching the `DMail` schema, specifying both the message text and target checkpoint index.

3. **Validation and Storage** – `SendDMail.__call__` invokes `DenwaRenji.send_dmail(dmail)`, which performs three strict validations:
   - Only one pending DMail may exist (`_pending_dmail is None`)
   - `checkpoint_id` must be non-negative
   - `checkpoint_id` must be less than `_n_checkpoints`
   
   Any violation raises `DenwaRenjiError`, propagating as a tool error to the LLM.

4. **Runtime Attachment** – After the current turn completes, **`KimiSoul`** (the core loop in [`src/kimi_cli/soul/kimisoul.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/kimisoul.py)) calls `DenwaRenji.fetch_pending_dmail()`. If a pending mail exists, the manager clears the slot and returns the DMail for attachment to the specified checkpoint, which then appears in the UI reply.

## Validation Guarantees

The `DenwaRenji` implementation enforces critical architectural constraints:

- **Single-Mail Guarantee**: The manager rejects multiple DMail requests within the same turn. If `send_dmail()` is called while `_pending_dmail` is populated, it raises **`DenwaRenjiError`**, preventing race conditions.

- **Checkpoint Bounds Checking**: The manager validates that `checkpoint_id` falls within the valid range `[0, _n_checkpoints)`, ensuring agents cannot reference non-existent checkpoints.

- **Side-Effect-Free Tool Execution**: `SendDMail` only records the request; actual checkpoint attachment happens later in the soul's main loop, keeping tool execution deterministic and free of UI side effects.

## Code Examples

### Manually Invoking the Tool

```python
from kimi_cli.tools.dmail import SendDMail
from kimi_cli.soul.denwarenji import DenwaRenji, DMail

# Initialize the manager (normally created by the runtime)

renji = DenwaRenji()
renji.set_n_checkpoints(5)               # Assume 5 checkpoints exist

# Build the DMail payload

mail = DMail(message="Deferred answer", checkpoint_id=3)

# Call the tool – this registers the pending DMail

send_tool = SendDMail(renji)
await send_tool(mail)                     # Raises DenwaRenjiError if invalid

```

### Fetching Pending Mail

```python

# Later in the main loop

pending = renji.fetch_pending_dmail()
if pending:
    # Attach pending.message to checkpoint pending.checkpoint_id

    attach_to_checkpoint(pending.checkpoint_id, pending.message)

```

### Testing Validation Logic

```python
def test_send_dmail_validation(send_dmail_tool: SendDMail):
    # Valid DMail

    await send_dmail_tool(DMail(message="ok", checkpoint_id=0))

    # Duplicate DMail in same turn → error

    with pytest.raises(DenwaRenjiError):
        await send_dmail_tool(DMail(message="second", checkpoint_id=0))

```

## Summary

- **Kimi CLI DMail** uses a three-component system: the `DMail` schema, `DenwaRenji` manager, and `SendDMail` tool.
- **Single-mail enforcement** prevents concurrent delayed messages within a single turn, ensuring deterministic ordering.
- **Validation** occurs at two levels: schema validation via Pydantic and runtime validation of checkpoint bounds against `set_n_checkpoints()`.
- **Decoupled attachment** means tools record intent in [`src/kimi_cli/soul/denwarenji.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/denwarenji.py) while `KimiSoul` handles actual UI integration after the turn completes.
- **Error handling** uses `DenwaRenjiError` to communicate validation failures back to the LLM as tool errors.

## Frequently Asked Questions

### What is DMail in Kimi CLI?

DMail (delayed mail) is a mechanism that allows an AI agent to send a message attached to a previously created checkpoint rather than the current conversation turn. This enables agents to defer status updates or summaries until after tool execution completes, implemented through the `DenwaRenji` manager in [`src/kimi_cli/soul/denwarenji.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/denwarenji.py).

### How does Kimi CLI prevent multiple delayed mails in one turn?

The `DenwaRenji` class enforces a single-mail guarantee by checking if `_pending_dmail` is `None` before accepting new mail. If the LLM attempts to send a second DMail before the current turn completes, `send_dmail()` raises a `DenwaRenjiError`, which propagates as a tool execution error.

### Can a DMail reference any checkpoint in the conversation history?

No. The `checkpoint_id` must be non-negative and strictly less than the current number of checkpoints tracked by `DenwaRenji._n_checkpoints`. The soul updates this count via `set_n_checkpoints()` during checkpoint creation, ensuring agents can only reference checkpoints that actually exist in the current session.

### Why is DMail attachment decoupled from the tool execution?

The `SendDMail` tool in [`src/kimi_cli/tools/dmail/__init__.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/tools/dmail/__init__.py) only records the request to `DenwaRenji`, while the actual attachment to the checkpoint UI happens later when `KimiSoul` calls `fetch_pending_dmail()`. This separation ensures tool execution remains side-effect-free and deterministic, allowing the runtime to control exactly when and how deferred messages appear in the conversation flow.