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

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 and src/kimi_cli/tools/dmail/__init__.py:

DMail Model

The DMail Pydantic schema (lines 6-10 in 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
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) 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
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, this tool receives the DMail payload and forwards it to the DenwaRenji manager without directly modifying UI state.

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) 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

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


# 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

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 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.

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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →