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 modelsSessionStartandSessionEndthat are passed as payloads to your hook functions when a session begins or ends.config.py– Contains theHookDefclass (schema for individual hooks) andHookConfigclass (manages loading and validation from user configuration).engine.py– Houses theHookEngineclass responsible for loading hook definitions, matching them to fired events, and executing the registered callables in sequence.runner.py– Provides theHookResultwrapper 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_startorsession_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:
SessionStartandSessionEndinsrc/kimi_cli/hooks/events.pyprovide 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.tomlwithevent,module, andcallablefields; theHookConfigclass validates these at startup. - Execution: The
HookEngineautomatically triggers registered functions whenKimiCLIcreates or destroys a session. - Extensibility: Use the
argsdictionary 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →