How to Write Custom Hooks to Trigger on SessionStart and SessionEnd Events in Kimi CLI

To write custom hooks that trigger on SessionStart and SessionEnd events, create a Python module with functions that accept the corresponding Pydantic event models, place it in an importable location (e.g., ~/.kimi/hooks/), and register the hook definitions in ~/.kimi/config.toml specifying the event type, module path, and callable name.

The MoonshotAI/kimi-cli repository provides a lightweight hook engine that lets you execute custom Python code during key session lifecycle transitions. By leveraging the SessionStart and SessionEnd events defined in the hooks system, you can automate setup tasks, logging, analytics, or cleanup operations without modifying the core CLI source code.

Understanding the Hook Architecture

The hook framework is implemented across several modules under src/kimi_cli/hooks/:

  • events.py – Defines the Pydantic models SessionStart and SessionEnd that are passed as payloads to your hook functions when a session begins or ends.
  • config.py – Contains the HookDef class (schema for individual hooks) and HookConfig class (manages loading and validation from user configuration).
  • engine.py – Houses the HookEngine class responsible for loading hook definitions, matching them to fired events, and executing the registered callables in sequence.
  • runner.py – Provides the HookResult wrapper that captures execution success, return values, and exception details.

When you launch the CLI, KimiCLI.create() in src/kimi_cli/app.py instantiates a KimiSession and immediately fires the SessionStart event through the hook engine. Conversely, KimiCLI.run() registers a signal handler that triggers the SessionEnd event via the engine just before the process exits, as managed in src/kimi_cli/soul/kimisoul.py.

Step-by-Step Implementation Guide

1. Create Your Hook Functions

Write a Python file containing functions that accept the appropriate event model as their first argument. The function signatures must match the event types defined in src/kimi_cli/hooks/events.py.


# ~/.kimi/hooks/lifecycle_hooks.py

from kimi_cli.hooks.events import SessionStart, SessionEnd
from loguru import logger
import datetime

def initialize_workspace(event: SessionStart) -> None:
    """
    Runs immediately after a new KimiSession is created.
    Access session metadata via the event payload.
    """
    logger.info(f"Session {event.session_id} started at {event.timestamp}")
    # Example: Create a temporary working directory for this session

    print(f"🚀 Initializing environment for session {event.session_id}")

def generate_summary(event: SessionEnd) -> None:
    """
    Runs just before the CLI exits and the session is torn down.
    """
    duration = datetime.datetime.now() - event.start_time
    logger.info(f"Session {event.session_id} ending. Duration: {duration}")
    # Example: Write analytics or clean up resources

    print(f"📊 Session lasted {duration.total_seconds()}s")

2. Register Hooks in config.toml

Add hook definitions to your user configuration file at ~/.kimi/config.toml. The HookConfig class parses this table and validates entries against the HookDef schema.


# ~/.kimi/config.toml

[[hooks]]
name = "workspace_initializer"
event = "session_start"
module = "lifecycle_hooks"
callable = "initialize_workspace"

[[hooks]]
name = "summary_generator"
event = "session_end"
module = "lifecycle_hooks"
callable = "generate_summary"

Each hook definition requires:

  • name – Unique identifier for the hook.
  • event – Either session_start or session_end.
  • module – Dotted Python import path (relative to directories in PYTHONPATH).
  • callable – Exact function name within the module.

3. Verify Hook Execution

Start a new CLI session to trigger the hooks:

$ kimi chat
🚀 Initializing environment for session a1b2c3d4
> Hello Kimi
...
^C
📊 Session lasted 45.2s

The HookEngine in src/kimi_cli/hooks/engine.py automatically invokes registered callables when the corresponding event fires. Errors are captured in HookResult objects and logged without crashing the CLI.

Advanced Configuration: Passing Arguments

You can supply static arguments to your hook functions via the args dictionary in the configuration. The engine passes these as keyword arguments when calling your function.

[[hooks]]
name = "detailed_logger"
event = "session_end"
module = "lifecycle_hooks"
callable = "log_to_file"
args = { log_level = "INFO", destination = "/tmp/kimi_audit.log" }

Corresponding function signature:

def log_to_file(event: SessionEnd, log_level: str, destination: str) -> None:
    """
    Receives the SessionEnd event plus config-defined arguments.
    """
    # Implementation using log_level and destination

    pass

Key Source Files Reference

File Purpose Key Components
src/kimi_cli/hooks/events.py Event model definitions SessionStart, SessionEnd Pydantic models
src/kimi_cli/hooks/config.py Configuration parsing HookDef, HookConfig.load()
src/kimi_cli/hooks/engine.py Hook execution logic HookEngine class, run_hook() method
src/kimi_cli/hooks/runner.py Result wrapping HookResult dataclass
src/kimi_cli/app.py Session initialization KimiCLI.create() fires SessionStart
src/kimi_cli/soul/kimisoul.py Shutdown handling Signal handlers fire SessionEnd

Summary

  • Event Models: SessionStart and SessionEnd in src/kimi_cli/hooks/events.py provide structured payloads containing session metadata.
  • Function Signature: Hook callables must accept the event object as the first positional argument.
  • Configuration: Define hooks in ~/.kimi/config.toml with event, module, and callable fields; the HookConfig class validates these at startup.
  • Execution: The HookEngine automatically triggers registered functions when KimiCLI creates or destroys a session.
  • Extensibility: Use the args dictionary in config to pass static parameters to your hooks without code changes.

Frequently Asked Questions

What data is available in the SessionStart and SessionEnd event payloads?

Both events are Pydantic models defined in src/kimi_cli/hooks/events.py. SessionStart typically includes session_id, timestamp, and the session configuration object. SessionEnd includes session_id, start_time, and end_time, allowing you to calculate duration and access final session state.

Where should I place my custom hook Python files?

Place your modules in any directory accessible via Python's import system. The standard convention is ~/.kimi/hooks/, but you can use any location provided you adjust the module path in config.toml accordingly (e.g., my_package.my_hooks if installed via pip, or relative paths if the directory is added to PYTHONPATH).

How do I debug a hook that is not firing?

First, verify your ~/.kimi/config.toml syntax is valid TOML and that the hooks table is properly formatted. Check the CLI logs for validation errors from HookConfig.load(). If the hook registers but fails during execution, the HookEngine captures exceptions in HookResult objects; enable verbose logging (--verbose flag) to see stack traces without crashing the session.

Can I register multiple hooks for the same session event?

Yes. The HookEngine supports multiple hook definitions targeting the same event type. They execute in the order defined in the configuration file. If one hook raises an exception, subsequent hooks in the chain still run unless the engine is configured otherwise, with errors recorded individually in their respective HookResult instances.

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 →