# How Lifecycle Hooks Gate Tool Calls and Drive Auditing in Kimi Code

> Discover how Kimi Code's lifecycle hooks gate tool calls and drive auditing. Learn about prepareToolExecution, authorizeToolExecution, finalizeToolResult, and onDidExecuteTool for enhanced control and insights.

- Repository: [Moonshot AI/kimi-code](https://github.com/MoonshotAI/kimi-code)
- Tags: deep-dive
- Published: 2026-07-25

---

**Kimi Code's agent engine intercepts every LLM tool invocation through a serializable pipeline of four lifecycle hooks—`prepareToolExecution`, `authorizeToolExecution`, `finalizeToolResult`, and `onDidExecuteTool`—enabling pre-execution gating, synthetic result injection, and comprehensive post-hoc auditing without modifying core scheduler logic.**

Kimi Code processes each conversational turn through an extensible **tool-call lifecycle** defined in [`packages/agent-core/src/loop/types.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/agent-core/src/loop/types.ts) and orchestrated within [`packages/agent-core/src/loop/tool-call.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/agent-core/src/loop/tool-call.ts). These **lifecycle hooks** provide injection points for security policies, argument validation, and audit logging while maintaining strict guarantees that every `tool.call` event pairs with exactly one `tool.result` event in the transcript.

## The Four Stages of the Tool-Call Lifecycle

The pipeline invokes hooks in provider order, ensuring deterministic execution even when multiple extensions register callbacks. Each stage serves a distinct purpose in the validation and observation pipeline.

### Hook Points and Execution Timing

The system exposes four primary interception points:

- **prepareToolExecution**: Runs after the LLM's tool-call payload parses but before recording the `tool.call` event. This hook validates or rewrites arguments, synthesizes cached results, or aborts execution entirely.
- **authorizeToolExecution**: Executes after pre-validation succeeds but before the `ToolScheduler` queues the operation. Enforces permissions, rate limits, or organizational policies.
- **finalizeToolResult**: Invokes after tool completion (or synthetic result generation) but before the turn concludes. Post-processes outputs, redacts sensitive data, or signals `stopTurn` to halt further processing.
- **onDidExecuteTool**: An L4 telemetry hook firing after transcript recording. Handles audit events, deduplication logic, and goal-service updates without affecting execution order or transcript fidelity.

### Decision Types and Flow Control

Gating hooks return specific decision enums that determine pipeline behavior:

- **PrepareToolExecutionDecision**: Returns `allowed`, `synthetic`, `blocked`, or `hookFailed`. A `blocked` or `hookFailed` decision short-circuits execution and emits a synthetic `tool.result` containing the hook's output.
- **AuthorizeToolExecutionResult**: Returns `allowed` or `blocked`. Blocking triggers immediate synthetic result generation, bypassing the scheduler entirely.

Because these decisions occur **before** the `ToolScheduler` receives the call, blocked operations never consume execution resources or trigger downstream resource-conflict handling.

## Gating Tool Calls Before Execution

The primary security mechanism combines `prepareToolExecution` and `authorizeToolExecution` to intercept calls before they reach the execution layer. When either hook returns a non-allowed decision, the pipeline synthesizes a result immediately:

```typescript
// Simplified flow from packages/agent-core/src/loop/tool-call.ts
if (hooks?.prepareToolExecution) {
  const prep = await hooks.prepareToolExecution(ctx);
  if (prep.kind !== 'allowed') return synthResult(prep);
}
if (hooks?.authorizeToolExecution) {
  const auth = await hooks.authorizeToolExecution(ctx);
  if (auth.kind !== 'allowed') return synthResult(auth);
}

```

A **blocked** decision prevents the call from reaching the tool executor, emitting a synthetic `tool.result` with the hook's explanatory output. The turn may optionally terminate via `stopTurn = true`. A **synthetic** decision allows the hook to supply a result directly—such as a cached response—bypassing actual execution while maintaining transcript consistency.

## Auditing Decisions and Post-Processing

Auditing occurs through hooks that execute **after** the tool result finalizes. The `finalizeToolResult` hook modifies or redacts data before it enters the transcript, while `onDidExecuteTool` observes the finalized record without altering execution order.

The most common audit pattern registers a listener on `toolExecutor.hooks.onDidExecuteTool`, as implemented in [`packages/agent-core/src/agent/turn/tool-dedup.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/agent-core/src/agent/turn/tool-dedup.ts):

```typescript
toolExecutor.hooks.onDidExecuteTool.register(
  'audit',
  async (ctx, next) => {
    // ctx contains toolCall, toolName, args, and result
    await auditService.record(ctx.toolCall.id, ctx.result);
    return next(); // continue the chain
  },
);

```

This L4 hook guarantees that the transcript remains a faithful, replayable log of the LLM-agent interaction while allowing extensions to react to decisions through external services, background tasks, or telemetry pipelines.

## Practical Implementation Patterns

### Blocking Dangerous Tools via prepareToolExecution

Register a validation hook to intercept unsafe operations before they execute:

```typescript
import { toolExecutor } from '@moonshot-ai/agent-core';

toolExecutor.hooks.prepareToolExecution.register(
  'block-dangerous-tools',
  async (ctx, next) => {
    if (ctx.toolName === 'delete-filesystem') {
      return {
        kind: 'blocked',
        args: ctx.args,
        output: 'Tool blocked by policy: unsafe filesystem deletion.',
      };
    }
    return next();
  },
);

```

The `delete-filesystem` tool never reaches the scheduler; instead, the transcript records a synthetic result containing the policy violation message.

### Synthesizing Cached Responses

Avoid redundant network requests by returning synthetic results for cache hits:

```typescript
toolExecutor.hooks.prepareToolExecution.register(
  'cached-search',
  async (ctx, next) => {
    if (ctx.toolName === 'web-search' && ctx.args.query === 'latest-price') {
      return {
        kind: 'synthetic',
        args: ctx.args,
        result: { content: 'Cached price: $123.45' },
      };
    }
    return next();
  },
);

```

The LLM receives the cached answer immediately, and the transcript contains a valid `tool.result` event without invoking the actual search provider.

### Recording Comprehensive Audit Trails

Capture every decision—including blocked and synthetic calls—for compliance analysis:

```typescript
import { toolExecutor } from '@moonshot-ai/agent-core';
import { auditService } from './audit-service';

toolExecutor.hooks.onDidExecuteTool.register(
  'record-audit',
  async (ctx, next) => {
    await auditService.log({
      id: ctx.toolCall.id,
      name: ctx.toolName,
      args: ctx.args,
      result: ctx.result,
      timestamp: Date.now(),
    });
    return next();
  },
);

```

This captures the complete execution context regardless of whether the result originated from a live tool, cache, or security block.

## Summary

- **Lifecycle hooks** in Kimi Code provide four injection points (`prepareToolExecution`, `authorizeToolExecution`, `finalizeToolResult`, `onDidExecuteTool`) for intercepting tool calls.
- **Gating** occurs through `prepareToolExecution` and `authorizeToolExecution`, which can block execution or synthesize results before the `ToolScheduler` consumes resources.
- **Synthetic results** maintain transcript integrity by ensuring every `tool.call` pairs with a `tool.result`, even when execution aborts.
- **Auditing** relies on `onDidExecuteTool` L4 hooks that observe finalized results without mutating transcript order, enabling telemetry and compliance logging.
- The pipeline wraps all steps in `try…finally` blocks (lines 80-84 of [`tool-call.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/tool-call.ts)) to settle spawned tasks and prevent dangling promises.

## Frequently Asked Questions

### What is the difference between prepareToolExecution and authorizeToolExecution?

**`prepareToolExecution`** runs first and handles argument validation, transformation, and caching logic, while **`authorizeToolExecution`** enforces policy decisions like permissions and rate limits after pre-validation succeeds. Both can block execution, but `prepareToolExecution` also supports argument rewriting and synthetic result injection, whereas `authorizeToolExecution` focuses strictly on access control.

### Can lifecycle hooks modify tool arguments before execution?

Yes. The `prepareToolExecution` hook receives the parsed arguments in `ctx.args` and can return a modified argument object within an `allowed` decision. This enables normalization, default value injection, or sanitization before the tool executor receives the payload.

### How does Kimi Code ensure audit logs capture blocked tool calls?

The pipeline emits synthetic `tool.result` events for any call blocked by `prepareToolExecution` or `authorizeToolExecution`. The `onDidExecuteTool` hook fires after transcript recording, meaning it observes both successful executions and synthetic results from blocked calls, ensuring comprehensive audit coverage without execution gaps.

### What happens to the transcript when a hook synthesizes a result?

The transcript remains consistent and replayable. When a hook returns a `synthetic` or `blocked` decision, the system generates a synthetic `tool.result` event containing the hook's output payload. This pairs with the original `tool.call` event in provider order, maintaining the strict one-to-one relationship required for accurate conversation reconstruction.