How to Write Lifecycle Hooks for Desktop Notifications in Kimi Code
Kimi Code provides a lifecycle-hook system that executes arbitrary commands at specific execution points, allowing plugins to trigger desktop notifications by reading environment variables like KIMI_TURN_ID and dispatching OS-native alerts or OSC 9 terminal sequences.
Kimi Code, the open-source agent framework from MoonshotAI, exposes a robust lifecycle-hook system that enables plugins to run commands when key events occur. By writing lifecycle hooks for desktop notifications in Kimi Code, you can alert users when agents finish turns, complete tool calls, or reach specific milestones. This guide walks through the architecture, implementation, and configuration required to build notification plugins using the actual source code from the MoonshotAI/kimi-code repository.
Understanding the Lifecycle Hook Architecture
The hook system relies on four core components that work together to bridge agent events and external notifications:
- Plugin manifest (
kimi.plugin.json): Declares which lifecycle hooks a plugin consumes and the command to run for each event. The schema is documented inapps/kimi-code/README.md. - Lifecycle-hook service (
sessionLifecycleService.ts): Located atpackages/agent-core-v2/src/app/sessionLifecycle/sessionLifecycleService.ts, this service registers hook callbacks, invokes the configured command as a child process, and captures its output. - Desktop-notification helper (
terminal-notification.ts): Found inapps/kimi-code/src/tui/utils/terminal-notification.ts, this utility emits an OSC 9 escape sequence that iTerm2, WezTerm, Kitty, and other terminals treat as a native desktop notification. - Configuration (
config-files.md): The global[notifications].enabledflag indocs/en/configuration/config-files.mdtoggles whether desktop notifications are delivered system-wide.
When a hook fires, the service injects context via environment variables—KIMI_TURN_ID, KIMI_SESSION_ID, KIMI_EVENT, and KIMI_TURN_OUTPUT—allowing the command to customize the notification based on the current execution state.
Creating a Desktop Notification Plugin
To build a plugin that alerts users when a turn finishes, you need three pieces: a manifest that registers the hook, a script that handles the event, and optionally a configuration override.
Step 1: Define the Plugin Manifest
Create a kimi.plugin.json file that binds the TurnFinished event to your notification script:
{
"name": "my-notify-plugin",
"version": "0.1.0",
"hooks": [
{
"event": "TurnFinished",
"command": "node ./scripts/notify.js"
}
]
}
The event field must match the enum values used by the session-lifecycle service, such as TurnFinished, ToolInvoked, or SessionCreated.
Step 2: Write the Notification Script
The script receives context through environment variables and should handle three execution paths: OSC 9 for compatible terminals, OS-native tools for standard desktops, and graceful fallbacks. Save this as scripts/notify.js:
#!/usr/bin/env node
// Simple desktop notification for a completed turn.
// Env vars: KIMI_TURN_ID, KIMI_SESSION_ID, KIMI_EVENT, KIMI_TURN_OUTPUT (JSON)
const { execSync } = require('child_process');
// Pull relevant data from the environment.
const turnId = process.env.KIMI_TURN_ID ?? 'unknown';
const sessionId = process.env.KIMI_SESSION_ID ?? 'unknown';
const output = process.env.KIMI_TURN_OUTPUT ?? '{}';
// Build a human‑readable title and body.
const title = `Kimi Code – Turn ${turnId} finished`;
const body = `Session ${sessionId} completed a turn.`;
const payload = JSON.parse(output);
// If the terminal supports OSC 9, delegate to the helper utility.
// (The helper lives in the main repo; we invoke it via node.)
try {
const helperPath = require('path')
.join(__dirname, '../../apps/kimi-code/src/tui/utils/terminal-notification.js');
execSync(`node ${helperPath} "${title}" "${body}"`);
} catch (_) {
// Fallback to a system‑specific notifier.
if (process.platform === 'darwin') {
// macOS: use AppleScript.
execSync(`osascript -e 'display notification "${body}" with title "${title}"'`);
} else if (process.platform === 'linux') {
// Linux: use notify‑send.
execSync(`notify-send "${title}" "${body}"`);
} else {
// Windows (PowerShell):
execSync(`powershell -Command "New-BurntToastNotification -Text '${title}', '${body}'"`);
}
}
This script first attempts to use the terminal-notification helper for OSC 9 compatibility. If that fails, it falls back to osascript on macOS, notify-send on Linux, or PowerShell's New-BurntToastNotification on Windows.
Step 3: Configure Global Notification Settings
Ensure notifications are enabled in your Kimi Code configuration. According to docs/en/configuration/config-files.md, set the following in your config file:
[notifications]
enabled = true
When enabled is false, the engine suppresses all desktop notification delivery regardless of hook configuration.
How the Hook Execution Flow Works
The sessionLifecycleService.ts file in packages/agent-core-v2/src/app/sessionLifecycle/ orchestrates the entire process. When the agent engine finishes a turn, it emits the TurnFinished event. The service then:
- Resolves the command mapped to that event in the plugin manifest.
- Spawns a child process executing that command with the injected environment variables (
KIMI_TURN_ID,KIMI_SESSION_ID, etc.). - Captures stdout and stderr for logging and debugging purposes.
- Evaluates the exit code; if the command returns non-zero, the hook is considered blocked. As implemented in
packages/agent-core/test/session/lifecycle-hooks.test.ts, the engine can optionally retry the action or abort the session based on this status.
This design allows hooks to act as gating mechanisms—for example, requiring user approval via a notification click before proceeding—or as simple fire-and-forget alerts.
Terminal-Specific Notifications with OSC 9
For users running modern terminal emulators, the terminal-notification.ts helper in apps/kimi-code/src/tui/utils/terminal-notification.ts provides a lightweight alternative to OS-native binaries. It constructs an OSC 9 escape sequence that terminals like iTerm2, WezTerm, and Kitty render as a desktop notification without spawning external processes.
You can invoke this helper directly from any hook command:
node apps/kimi-code/src/tui/utils/terminal-notification.js "Kimi Code" "Turn completed"
The helper automatically truncates payloads to stay within terminal emulator limits, ensuring the notification always renders even with long titles or bodies.
Summary
- Lifecycle hooks in Kimi Code are declared in
kimi.plugin.jsonand executed bysessionLifecycleService.tsat specific agent events likeTurnFinished. - Environment variables (
KIMI_TURN_ID,KIMI_SESSION_ID,KIMI_EVENT,KIMI_TURN_OUTPUT) provide context to hook scripts, enabling dynamic notification content. - OSC 9 escape sequences via
terminal-notification.tsoffer a fast, dependency-free notification path for compatible terminals, while fallbacks tonotify-send,osascript, or PowerShell cover standard desktops. - Error handling treats non-zero exit codes as blocked hooks, allowing the engine to retry or abort according to logic tested in
packages/agent-core/test/session/lifecycle-hooks.test.ts. - Global toggles in
docs/en/configuration/config-files.mdlet users disable all notifications via the[notifications].enabledsetting.
Frequently Asked Questions
What events can trigger lifecycle hooks in Kimi Code?
The system supports events such as TurnFinished, ToolInvoked, and SessionCreated, as defined in the lifecycle service enum. You bind to these events in your plugin's kimi.plugin.json manifest by specifying the exact event name in the event field of the hooks array.
How do I disable desktop notifications globally without removing my plugin?
Set enabled = false under the [notifications] section in your Kimi Code configuration file. This setting, documented in docs/en/configuration/config-files.md, suppresses all desktop notification delivery system-wide while keeping your lifecycle hooks registered for other side effects.
What happens if my notification script exits with an error?
If your hook command returns a non-zero exit status, the lifecycle service marks the hook as blocked. According to the test suite in packages/agent-core/test/session/lifecycle-hooks.test.ts, the engine can be configured to either retry the blocked action or abort the session entirely, making hooks suitable for approval gates as well as notifications.
Can I use the terminal notification helper outside of lifecycle hooks?
Yes. The terminal-notification.ts utility is a standalone script that emits OSC 9 escape sequences. You can invoke it directly from the command line or from any Node.js script to send notifications to iTerm2, WezTerm, or Kitty, regardless of whether it is triggered by the Kimi Code hook system.
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 →