How to Implement Tool Approval and Permission Gating Mechanisms in Kimi-Code

Kimi-Code implements tool approval and permission gating through a dual-layer architecture that separates human-in-the-loop approval flows from automated policy enforcement using permission modes and rules.

MoonshotAI/kimi-code is an open-source AI coding agent framework that isolates tool execution behind two complementary mechanisms. Understanding how to implement tool approval and permission gating mechanisms allows you to build secure, policy-compliant agent workflows that require explicit human authorization for sensitive operations while automatically approving safe, repetitive tasks.

The Approval Flow: From Emission to Resolution

When an agent issues a tool call, Kimi-Code creates a structured approval workflow that persists in the session transcript and exposes REST endpoints for client interaction.

Creating ToolCallFrame and Pending Interactions

The framework instantiates a ToolCallFrame that may contain an approvalId when a tool call requires supervision. According to the source code in packages/transcript/src/model/interaction.ts, this links to an Interaction object with kind: 'approval' stored in the transcript. The session's pending_interaction field transitions to 'awaiting_approval', as defined in packages/klient/src/core/facade/session.ts.

The pending state remains until resolved via the REST API or automated rules. All approval state schemas are strictly validated using Zod in packages/protocol/src/approval.ts.

Fetching Pending Approvals via REST

Clients poll for pending approvals using the endpoint defined in packages/protocol/src/rest/approval.ts:

GET /v1/sessions/{session_id}/approvals?status=pending

This returns the approval_id, tool name, input preview, and expiration. The response structure follows approvalResponseSchema, ensuring type safety between server and client implementations.

Resolving Approval States

To resolve a pending approval, the client submits a POST request to the same endpoint with an ApprovalResponse payload:

POST /v1/sessions/{session_id}/approvals/{approval_id}

Valid decisions include approved, rejected, or cancelled. The server validates against approvalResponseSchema before updating the transcript entry and transitioning the session status from awaiting_approval back to running. This logic is implemented in packages/agent-core/src/services/approval/approval.ts.

Configuring Permission Gating with Rules

Permission gating determines the default policy for whether tool calls require explicit approval, controlled through Session.agent_config.permission_mode and PermissionRule objects defined in packages/protocol/src/session.ts.

Understanding Permission Modes

The permission_mode field accepts three string values that dictate engine behavior:

  • manual – Every tool call generates a pending approval, ensuring maximum human oversight.
  • auto – Calls proceed automatically only if a matching PermissionRule exists; otherwise falls back to manual review.
  • yolo – All calls auto-approve immediately, bypassing the entire approval pipeline (use with caution).

The default mode is configurable at the server level via default_permission_mode, as demonstrated in packages/node-sdk/test/create-session-transport.test.ts.

Defining PermissionRule Objects

Permission rules follow the permissionRuleSchema structure stored in session.permission_rules. Each rule contains:

  • tool_name – The target tool identifier (e.g., "Bash", "FileWrite").
  • matcher – Optional conditional filter supporting command_prefix, path_glob, exact_input, or always.
  • decision – Currently supports only 'approved', with planned extensibility for rejection rules.

When the engine evaluates a tool call, it executes the shouldAwaitApproval(toolName, input) function in packages/agent-core/src/services/approval/approval.ts. This function checks the current mode and rule set, returning true only when a manual approval is required.

Runtime Engine Integration

The approval pipeline integrates deeply with session lifecycle management and transcript publishing.

Session Creation and Dynamic Updates

When initializing a session, clients specify the initial permission_mode and rule set through sessionCreateSchema in packages/protocol/src/session.ts. The specification allows pre-configuring safe tools for automatic approval while flagging sensitive operations for manual review.

Clients may update permissions at runtime using sessionUpdateSchema. This patches session.permission_rules and immediately affects subsequent tool call evaluations without requiring session restart.

Transcript Publishing and UI Synchronization

The transcript service publishes awaiting_approval status changes, enabling UI clients like kimi-web and kimi-code to surface notification badges and approval queues. Each Interaction object maintains a link to its corresponding ToolCallFrame via approvalId, creating a complete audit trail of human decisions within the session transcript.

Implementation Examples

Define a Session with Auto-Approval for Specific Tools

Create a session that automatically approves Bash tool calls while requiring manual review for others:

import { SessionCreate } from '@moonshot-ai/protocol';
import { SessionClient } from '@moonshot-ai/klient';

const sessionSpec: SessionCreate = {
  title: 'Demo session',
  agent_config: {
    permission_mode: 'auto',
  },
  permission_rules: [
    {
      id: 'rule-1',
      tool_name: 'Bash',
      matcher: { kind: 'always' },
      decision: 'approved',
      created_at: new Date().toISOString(),
      created_by: 'user',
    },
  ],
};

await SessionClient.create(sessionSpec);

This configuration references the sessionCreateSchema available in packages/protocol/src/session.ts.

Fetch Pending Approvals

Monitor active sessions for tool calls awaiting human review:

import { SessionClient } from '@moonshot-ai/klient';

const pending = await SessionClient.getPendingApprovals(sessionId);
for (const appr of pending) {
  console.log(`Approve ${appr.tool_name} call ${appr.tool_call_id}?`);
}

The underlying endpoint is defined in packages/protocol/src/rest/approval.ts.

Resolve an Approval Decision

Programmatically approve or reject pending tool calls:

import { SessionClient } from '@moonshot-ai/klient';

await SessionClient.resolveApproval(sessionId, approvalId, {
  decision: 'approved',
  // optional: scope: 'session', feedback: 'looks good'
});

This implements the approvalResolveRequestSchema from the protocol definitions.

Change Permission Mode at Runtime

Switch from auto-approval to manual oversight mid-session:

await SessionClient.update(sessionId, {
  permission_mode: 'manual',
});

This utilizes the sessionUpdateSchema validation in packages/protocol/src/session.ts.

Summary

  • Dual-layer security: Kimi-Code separates human approval workflows (Interaction objects) from automated policy enforcement (permission_mode and PermissionRule).
  • Three permission modes: Configure sessions using manual (always approve), auto (rule-based), or yolo (always auto-approve) via packages/protocol/src/session.ts.
  • RESTful approval API: Manage pending approvals through GET and POST endpoints defined in packages/protocol/src/rest/approval.ts, with strict Zod schema validation.
  • Engine decision logic: The shouldAwaitApproval() function in packages/agent-core/src/services/approval/approval.ts evaluates rules and modes to determine if a tool call requires human intervention.
  • Transcript integration: All approval states persist as Interaction objects linked to ToolCallFrame instances, creating complete audit trails within session transcripts.

Frequently Asked Questions

What is the difference between permission_mode 'auto' and 'yolo'?

auto mode requires explicit PermissionRule definitions to auto-approve specific tools; any unmatched tool call falls back to manual approval. yolo mode bypasses all rule checking and immediately approves every tool call without creating pending Interaction objects or transcript entries, suitable only for trusted, isolated environments.

How do I create permission rules that match specific command patterns?

Use the matcher field within your PermissionRule object to specify conditional logic. The matcher supports command_prefix (approves commands starting with specific strings), path_glob (file path patterns), exact_input (string matching), or always (unconditional approval for the tool). Define these in sessionCreateSchema or update them dynamically via sessionUpdateSchema.

Where does the approval decision logic execute in the codebase?

The core evaluation occurs in the shouldAwaitApproval(toolName, input) function within packages/agent-core/src/services/approval/approval.ts. This service checks the current session's permission_mode and iterates through permission_rules to determine whether to create a pending Interaction or allow immediate execution.

Can I implement custom approval UIs using the protocol definitions?

Yes. The protocol package exposes all necessary schemas and REST endpoint types in packages/protocol/src/rest/approval.ts and packages/protocol/src/approval.ts. You can build custom clients that poll GET /v1/sessions/{session_id}/approvals and submit resolutions via POST /v1/sessions/{session_id}/approvals/{approval_id}, following the approvalResponseSchema for payload validation.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →