Runtime overview
Beacon supports Kimi Code — Moonshot AI’s coding agent, run askimi — through the runtime’s own lifecycle hooks, sent to beacon-hooks.
--harness kimi-code and --harness kimi-code-cli are also accepted, and all three install the same thing.
Kimi Code runs one agent core behind several front ends — the terminal CLI, the desktop app, the VS Code extension, and the ACP server — and every one of them reports the same client identity to a hook (client_type: "kimi_code_cli"). So there is one integration rather than one per surface, and one harness name, kimi_code, rather than a name per surface.
Kimi Code ships no OpenTelemetry export. Its only telemetry switch is
KIMI_DISABLE_TELEMETRY, which governs Moonshot’s own anonymous product reporting and has nothing to do with Beacon. The hook system is the whole integration surface, so there is no endpoint to point at the local collector.Prerequisites
Before enabling Kimi Code telemetry, make sure:kimiis installed. It does not need to have been run — Beacon will createconfig.tomlif it is missing.beacon endpoint installhas run so shared endpoint config and runtime log paths exist.- A new Kimi Code session is started after hook install or removal.
config.tomlis read once at startup, so a running session will not pick the change up;/reloadalso works.
Collection path
Beacon registers its hooks in the[[hooks]] array of one file:
$KIMI_CODE_HOME defaults to ~/.kimi-code, and replaces it when set — matching how Kimi Code resolves its data root itself.
Beacon appends and never re-serializes
Beacon appends its[[hooks]] entries to the end of the document and leaves every byte above them exactly as it found them. Nothing is parsed and written back, so a secret cannot be rewritten, re-quoted, reordered, or dropped by a round trip — and a comment next to one cannot be lost.
Appending is sound rather than merely convenient: an array-of-tables header at the end of a TOML document opens a new table and cannot change the meaning of anything before it. That was verified against smol-toml, the exact parser Kimi Code uses, not just against TOML 1.0 in principle.
Every write is then parsed back and compared before it replaces the file: every top-level key other than hooks must be deeply equal to what was there, and hooks must be exactly the intended entries. A scanning or quoting mistake fails the install with an explanation instead of damaging the file. This is the same check Kimi Code’s own config writer performs before replacing it.
Beacon refuses rather than writing:
- a
config.tomlit cannot parse. Kimi Code refuses to start on a config it cannot load, so writing into one would replace a problem you can see with one Beacon caused; - a
config.tomlwhosehookskey is an inline array (hooks = [{ ... }]). TOML does not allow[[hooks]]entries after one, so appending would leave a config Kimi Code will not start with. Rewrite those hooks as[[hooks]]blocks and install again.
beacon endpoint discover reports that second case as misconfigured before you hit it.
Beacon’s block
kimi-cli rewrites config.toml by serializing the merged config, which keeps the [[hooks]] entries and drops every comment in the document. A marker-based uninstall would silently fail to find its own block on any machine that had been through that upgrade.
Registered events
Kimi Code exposes twenty hook events. Beacon registers thirteen:
Timeouts are seconds, and Kimi Code validates the field as an integer in 1–600 and refuses to load the whole config file outside that range. Its own default is 30 seconds; Beacon writes explicit values as ceilings on a hang, not budgets.
Beacon’s hooks carry no
matcher. An absent matcher is the match-all form, which is what a telemetry hook wants on every event — and a matcher that is present is compiled as a regular expression, where one Kimi Code cannot compile matches nothing at all, silently.
The seven events Beacon does not register:
SessionHeartbeatis the one where subscribing would create the behavior rather than observe it. Kimi Code starts its sixty-second timer only when a hook is configured for it, so Beacon would be spawning a process a minute in every session to write an event saying nothing happened.UserPromptQueuedfires when a message is typed while a turn is still running; the prompt is recorded when it is submitted, and recording the queueing too would count one prompt twice.TurnStartedcarries a turn id the endpoint schema has no field for, and itspromptis the textUserPromptSubmitalready carried.TaskStartedandNotificationreport background tasks, and the tool that started one — aBashwithrun_in_background, anAgent, anAskUserQuestion— is already recorded as itself.StopFailurereports a turn that failed on an API or runtime error rather than anything the agent did.Interruptfires in place ofStopwhen you cancel a turn. Beacon’s closing action istool.completed, which states the agent finished; a cancelled turn did not, and there is no action in the endpoint schema that says so. The session is still closed bySessionEnd.
Discovery and status
Show Kimi Code hook status
kimi_code harness with capability hooks, and every event carries harness.collection_method=hook. Kimi Code has a plugin system that can declare hooks, and Beacon deliberately does not use it — a plugin install needs an interactive step and a /reload, and choosing it would have changed that field as a side effect of an install mechanic rather than of what runs on each event.
beacon endpoint inventory lists config.toml as a native config rather than a hook config, and reports beacon_managed only when the hook command is present. On this runtime “the file exists” says nothing: it exists on every machine with Kimi Code installed.
Install or configuration support
Telemetry coverage
Tool taxonomy
Kimi Code publishes its built-in tools, so classification is exhaustive over the reference rather than inferred from names:Glob and Grep are in that table because the generic classifier every runtime shares gets them wrong, and inconsistently: “grep” is in its read list and “glob” is in none of them, so one search would record a file operation and its sibling would record nothing. Both take the path they searched, and both are recorded as reads of it.
CronCreate, CronList, TaskList, and TodoList are entered for the opposite reason — they contain “create” and “list”, which the substring rule answers for, and they touch no file.
Write is a create or a modification, depending on mode
Write takes an optional mode of overwrite (the default) or append. Beacon reads it, because the difference is not cosmetic: a create-shaped diff over an append’s content emits @@ -0,0 +1,N @@, which asserts that the file’s entire contents are the fragment that was just added to the end of it.
An appending Write is recorded as file.modified with operation modify, and its diff states the true thing instead — these lines were added, and nothing is said about what was there before.
Tool output, and where the exit code lives
Kimi Code sends no tool-response object. A successful call carriestool_output as a plain string, and a failed one carries no tool_output at all — its output is flattened into an error object, under error.message. Beacon reads both, so a failing command’s own stderr reaches the log rather than being dropped.
Bash reports a non-zero exit by appending Command failed with exit code: N. to that text, and that sentence is the only place an exit code reaches a hook. Beacon reads the last one: everything above it is the command’s own output, and a command that prints something looking like the sentence — a test asserting on one, a log line quoting one — would otherwise decide the event’s exit code.
A missing marker is not read as exit 0. On this runtime a Bash result with no marker is either a command that exited cleanly or a background command that has only just been launched and whose result is a task id, and those are different facts.
A failed write is not recorded as an edit.
PostToolUseFailure carries the same tool_name and tool_input as a success — Kimi Code rejects a write whose file changed on disk since the session last read it, and an Edit whose old_string matches more than once — so without a guard Beacon would build a diff from the content of a write that never landed and assert the file changed. Those arrive as tool.failed with no diff.Approvals and enforcement
Beacon records real operator approval decisions for Kimi Code. This is one of the few supported runtimes where it can. Kimi Code firesPermissionRequest just before it blocks on a person and PermissionResult once they have answered. The second carries the whole request back plus the answer, so these events are observed rather than inferred — unlike the approvals Beacon synthesizes on runtimes that expose only a pre-tool notification, and unlike Cline, Pi, fx, Kiro, and goose, where Beacon refuses to synthesize one at all.
Cancelled and error are recorded as denials because that is what happened to the call: it did not run, and a reader counting blocked tool calls should see them. Their
approval.decision distinguishes them from a person saying no.
The decided call’s own arguments ride along, so an approval for a shell command records the command line and one for a write records the path. Every approval rule Beacon ships matches on command.command or file.path rather than on a tool name, so an approval that said only “the operator denied Bash” would be telemetry no rule could act on.
Two details with no schema field of their own survive under raw.kimi: scope, which is session when you approved every future call like this one rather than just this one, and feedback, the reason you typed when refusing.
PreToolUse does not also become an approval. On this runtime it fires before the permission check runs, so at that moment nobody has been asked anything.
Enforcement
Enforcement stays behind the optional, off-by-default policy provider seam (BEACON_POLICY_PROVIDER), which is inert unless it names an executable and fails open on any error. On Kimi Code a provider deny is exit code 2 with the reason on stderr, which the runtime checks before it parses stdout at all.
Data handling
Kimi Code content is handled like every other runtime. Prompts, tool arguments, commands, command output, paths, and diffs are retained in local or customer-controlled logs, with local secret redaction and per-string limits applied before writing, and a hash and byte count on each content-bearing event. Events over the 64 KiB limit drop raw and retained content, preserve stable metadata, and setfield_truncated.
Beacon preserves the verbatim payload under raw.kimi, as it does for Qwen Code, Muse Code, and DeepSeek Harness. Non-MCP tool arguments reach no endpoint-schema field on any runtime, so on Kimi Code this is where a Grep pattern, an Agent’s prompt, a CronCreate schedule, and a Bash run_in_background flag survive — along with the approval scope and feedback above, session_title, client_type, is_steer on a prompt, source and profile on SessionStart, and reason on SessionEnd. The cost is stated rather than hidden: on a Write the payload repeats the file content the diff already carries. raw is the first thing dropped at the size ceiling, so the duplication is bounded and the diff is what survives.
Beacon writes nothing to a Kimi Code hook’s stdout. On
UserPromptSubmit, a hook that exits 0 has its stdout appended to the model’s context inside a <hook_result> element — so the {} Beacon’s observing hooks otherwise finish with would be pasted in front of the model once per prompt. Suppressing it costs nothing: Kimi Code ignores hook stdout on every other event, and the one deny shape Beacon needs is an exit code.Known gaps
- No token usage or cost. No hook payload carries a usage count on any of the twenty events. Kimi Code does count tokens — the TUI shows context occupancy and
PreCompactcarries atoken_countfor the history it is about to summarize — but neither is per-call spend, and promoting either intogen_ai.usagewould make a level look like an additive total. Kimi Code activity therefore does not appear inbeacon token-usagereports or the dashboard token view, and coverage reports it as expected-none rather than silent. - No agent message text. Kimi Code puts no assistant response on any hook payload, so sessions are recorded with the user’s half and the agent’s actions but not the agent’s prose.
- No interrupted-turn event.
Interruptfires in place ofStopwhen you cancel a turn, and Beacon does not register it; recording a cancellation astool.completedwould state the agent finished. The session still closes throughSessionEnd. - No heartbeat.
SessionHeartbeatis deliberately unregistered — see Registered events. - No project scope. See the warning under Collection path.
- Hooks are fail-open in every direction. A hook that times out, crashes, or exits non-zero other than 2 is treated as an allow, and nothing surfaces. That is Kimi Code’s documented design and is why the runtime’s own guidance is not to use hooks as a sole security barrier.
Deployment notes
config.toml is read at startup, so an install taken while Kimi Code is running takes effect on the next session. The install prints that reminder.
Confirm status, then generate one event and check the log:
- Check
$KIMI_CODE_HOME: if it is set, the config is under that directory, not~/.kimi-code. - Confirm
config.tomlcontains[[hooks]]entries with--platform kimi. - Run
beacon endpoint discover— it reportsmisconfiguredfor a config that is not valid TOML or that defineshooksas an inline array. - Start a new Kimi Code session, or run
/reload. - Check
$KIMI_CODE_HOME/logs/for a hook error. A hook that cannot start is fail-open and says so only there.
Related
Supported agent harnesses
Return to the runtime support overview.
Agent harness integration model
See how Beacon discovers and configures each runtime.