How the Kimi-Code Telemetry Event System Works: Architecture, Client API, and Naming Conventions
The kimi-code telemetry system uses a compile-time event registry with strict TypeScript-enforced naming conventions, a singleton TelemetryClient for buffering and sanitization, and pluggable sinks for event delivery.
This deep dive into the MoonshotAI/kimi-code repository explores how the telemetry event system is architected, how developers should interact with it, and the exact naming conventions that must be followed to maintain type safety and data privacy compliance.
Core Architecture: Three-Layer Design
The telemetry system separates concerns into distinct layers that work together to ensure type-safe event collection.
Event Registry
All telemetry events must be declared in the compile-time registry at packages/agent-core-v2/src/app/telemetry/events.ts. The telemetryEventDefinitions constant holds the complete list of valid events, with each entry created through factory functions:
defineTelemetryEvent()— for plain, global eventsdefineAgentTelemetryEvent()— for agent-scoped events that automatically includeagent_id
The registry exports helper types (TelemetryEventName, TelemetryEventPayload, TelemetryEventProperties) that let the rest of the codebase infer exact payload shapes from event names. This eliminates string-typing errors and guarantees that every tracked event matches its declared schema.
Telemetry Client
The TelemetryClient singleton in packages/telemetry/src/client.ts (lines 20–30) handles runtime event processing:
| Capability | Implementation |
|---|---|
| Context management | setContext() / withContext() for device and session IDs |
| Event recording | track(event, properties) and trackWithContext() methods |
| Queueing | In-memory buffer with MAX_QUEUE_SIZE = 1000, oldest-events dropped on overflow |
| Sanitization | sanitizeProperties() function (lines 89–96) strips non-primitive values |
| Shutdown | flush() and shutdown() with configurable timeout |
When track() is called, the client generates a UUID for event_id, populates context identifiers, runs sanitization, wraps the result in a TelemetryEvent object, and either queues it or passes it directly to the attached sink.
Transport and Sink Layer
Concrete delivery implementations live in packages/telemetry/src/transport.ts (HTTP remote transport) and packages/telemetry/src/sink.ts (file-based local buffer). The sink interface allows custom backends without modifying the core client.
Telemetry Event Naming Conventions
The following conventions are enforced by TypeScript at compile time and documented in the header of events.ts (lines 11–14).
Event Names: snake_case
Every event identifier must use lowercase snake_case.
- ✅
turn_started - ✅
tool_call - ❌
turnStarted - ❌
ToolCall
Property Keys: snake_case
All payload property keys follow the same casing rule.
Unit Suffixes for Numeric Properties
When a number represents a measurable quantity, append the unit:
| Suffix | Use Case | Example |
|---|---|---|
_ms |
Durations | duration_ms, latency_ms |
_count |
Cardinalities | retry_count, token_count |
_bytes |
Data sizes | payload_bytes, heap_bytes |
Privacy Rule: No User Content
Telemetry properties must never contain raw user input, file paths, personally identifiable information, or any data that could identify an individual. The sanitizeProperties function provides runtime defense, but compile-time review is the primary safeguard.
Agent-Scoped Events
Use defineAgentTelemetryEvent() for any event emitted within an agent context. This automatically injects agent_id via AgentTelemetryEventContext and agentTelemetryContextProperties, ensuring consistent attribution without manual key management.
Code Examples: Recording Telemetry Events
Basic Global Event
import { track } from '#/telemetry/client';
// Fire-and-forget recording of a session lifecycle event
track('session_started', { resumed: true });
Agent-Scoped Event with Full Context
import { withContext } from '#/telemetry/client';
const agentClient = withContext({
deviceId: 'dev-123',
sessionId: 'sess-456'
});
agentClient.track('turn_started', {
turn_id: 7,
mode: 'agent',
duration_ms: 125, // Unit suffix mandatory
});
Custom File Sink Integration
import { attachSink } from '#/telemetry/client';
import { FileSink } from '#/telemetry/sink';
const sink = new FileSink('/tmp/telemetry.log');
attachSink(sink);
// All subsequent track() calls persist to disk
Type Safety Enforcement
The system uses TelemetryEventMeta in events.ts to type properties as Readonly<Record<string, string>> where keys must exactly match the declared payload interface. This produces compile-time errors for:
- Misspelled property names
- Missing required fields
- Incorrect value types
- Violation of unit suffix conventions (via naming lint rules)
Key Source Files
| File Path | Responsibility |
|---|---|
packages/agent-core-v2/src/app/telemetry/events.ts |
Event registry, naming convention definitions, type helpers |
packages/telemetry/src/client.ts |
Core client: queueing, sanitization, context injection |
packages/telemetry/src/types.ts |
Primitive types: TelemetryPrimitive, TelemetryProperties, TelemetryEvent |
packages/telemetry/src/sink.ts |
Sink interface and file-based implementation |
packages/telemetry/src/transport.ts |
HTTP transport for server upload |
Summary
- Event registration happens at compile time in
events.tsusingdefineTelemetryEventordefineAgentTelemetryEvent - Runtime recording flows through the
TelemetryClientsingleton with automatic UUID generation, context injection, and primitive-only sanitization - Naming conventions (
snake_case, unit suffixes, privacy exclusions) are TypeScript-enforced and prevent schema drift - Extensible delivery via the sink pattern supports file, HTTP, or custom backends without core changes
Frequently Asked Questions
How do I add a new telemetry event to the codebase?
Define it in packages/agent-core-v2/src/app/telemetry/events.ts using defineTelemetryEvent() for global events or defineAgentTelemetryEvent() for agent-scoped events. The registry entry specifies the exact payload shape; TypeScript will then enforce this shape on all track() calls throughout the codebase.
What happens if I send the wrong property type in a telemetry event?
The sanitizeProperties function (lines 89–96 of client.ts) strips any value that isn't a TelemetryPrimitive (boolean | number | string | undefined | null). Non-primitives are silently dropped to prevent serialization failures and data leakage. For type safety, the registry should catch most mismatches at compile time.
Can I include user file paths in telemetry for debugging purposes?
No. The naming conventions explicitly forbid raw user data, file paths, and personally identifiable information in telemetry properties. This is a hard privacy requirement, not merely a style guideline. Use anonymized identifiers or hashes if path correlation is absolutely necessary.
How does the telemetry queue handle high-volume scenarios?
The client maintains an in-memory queue with a hard limit of 1,000 events (MAX_QUEUE_SIZE). When this limit is exceeded, the oldest events are unconditionally dropped. For production deployments, configure a persistent sink (file or HTTP) and call flush() or shutdown(timeout) during application lifecycle events to minimize data loss.
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 →