From 9cc43332ba2fd18c9bbb0c3e90fb5fbbb9d1fe4b Mon Sep 17 00:00:00 2001 From: liukx0205 <69756503+liukx0205@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:48:43 +0000 Subject: [PATCH 1/3] feat(agent-core-v2): allow read-only tools in /btw side questions The btw child agent previously vetoed every tool call. Allow the read-only tools Read, Grep, and Glob so side questions about the codebase can be answered from current file contents; write and execute tools stay disabled. --- .changeset/btw-readonly-tools.md | 5 ++ docs/en/reference/server-api.md | 2 +- docs/zh/reference/server-api.md | 2 +- .../agent-core-v2/src/features/btw/btw.ts | 17 +++--- .../src/features/btw/btwService.ts | 11 +++- .../test/features/btw/btw.test.ts | 54 +++++++++++++------ 6 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 .changeset/btw-readonly-tools.md diff --git a/.changeset/btw-readonly-tools.md b/.changeset/btw-readonly-tools.md new file mode 100644 index 00000000000..05e0478e050 --- /dev/null +++ b/.changeset/btw-readonly-tools.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": minor +--- + +Add read-only tools to the /btw side agent. It can now read and search files to answer questions about the codebase; write and execute tools stay disabled. diff --git a/docs/en/reference/server-api.md b/docs/en/reference/server-api.md index 33948cd726b..3fbb866d100 100644 --- a/docs/en/reference/server-api.md +++ b/docs/en/reference/server-api.md @@ -751,7 +751,7 @@ On success, `data` is `{ aborted: true }`. #### `POST /api/v1/sessions/{session_id}:btw` -Starts a "by the way" side conversation: forks the main agent into a child agent whose tool calls are disabled, so quick side questions run in isolation without touching the working context. Requires a usable model configuration. +Starts a "by the way" side conversation: forks the main agent into a child agent whose tool calls are limited to the read-only tools Read, Grep, and Glob, so quick side questions run in isolation without touching the working context. Requires a usable model configuration. On success, `data` is `{ agent_id }` — the id of the new child agent. diff --git a/docs/zh/reference/server-api.md b/docs/zh/reference/server-api.md index 2652e0a980b..ee8b250fc8e 100644 --- a/docs/zh/reference/server-api.md +++ b/docs/zh/reference/server-api.md @@ -751,7 +751,7 @@ schema 还接受 `agent_config` 内的 `system_prompt`、`tools`、`mcp_servers` #### `POST /api/v1/sessions/{session_id}:btw` -开启一个 `"by the way"` 旁路对话:把 main agent fork 成一个禁用工具调用的子 Agent,让快速的临时问题在隔离环境中运行,不触碰工作上下文。需要可用的模型配置。 +开启一个 `"by the way"` 旁路对话:把 main agent fork 成一个仅可使用只读工具(Read、Grep、Glob)的子 Agent,让快速的临时问题在隔离环境中运行,不触碰工作上下文。需要可用的模型配置。 成功时,`data` 为 `{ agent_id }`——新子 Agent 的 id。 diff --git a/packages/agent-core-v2/src/features/btw/btw.ts b/packages/agent-core-v2/src/features/btw/btw.ts index 00a09e751bf..7fa91fc3f89 100644 --- a/packages/agent-core-v2/src/features/btw/btw.ts +++ b/packages/agent-core-v2/src/features/btw/btw.ts @@ -1,19 +1,22 @@ import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; +export const BTW_READONLY_TOOLS = new Set(['Read', 'Grep', 'Glob']); + export const TOOL_CALL_DISABLED_MESSAGE = - 'Tool calls are disabled for side questions. Answer with text only.'; + 'Only the read-only tools Read, Grep, and Glob are available for side questions. Other tool calls are disabled.'; export const SIDE_QUESTION_SYSTEM_REMINDER = ` -This is a side-channel conversation with the user. You should answer user questions directly based on what you already know. +This is a side-channel conversation with the user. You should answer user questions directly. IMPORTANT: - You are a separate, lightweight instance. - The main agent continues independently; do not reference being interrupted. -- Do not call any tools. All tool calls are disabled and will be rejected. - Even though tool definitions are visible in this request, they exist only - for technical reasons (prompt cache). You must not use them. -- Respond only with text based on what you already know from the conversation - and this side-channel conversation. +- You may use the read-only tools Read, Grep, and Glob to inspect files when + the answer depends on current file contents. All other tools are disabled + and will be rejected, even though their definitions are visible in this + request (they exist only for technical reasons — prompt cache). +- Prefer answering from what you already know from the conversation and this + side-channel conversation; reach for the read-only tools only when needed. - Follow-up turns may happen in this side-channel conversation. - If you do not know the answer, say so directly. `.trim(); diff --git a/packages/agent-core-v2/src/features/btw/btwService.ts b/packages/agent-core-v2/src/features/btw/btwService.ts index 933e5995b98..aca4b12825c 100644 --- a/packages/agent-core-v2/src/features/btw/btwService.ts +++ b/packages/agent-core-v2/src/features/btw/btwService.ts @@ -6,7 +6,12 @@ import { IAgentScopeContext } from '#/agent/scopeContext/scopeContext'; import { ErrorCodes, Error2 } from '#/errors'; import { IAgentLifecycleService, MAIN_AGENT_ID } from '#/session/agentLifecycle/agentLifecycle'; -import { ISessionBtwService, SIDE_QUESTION_SYSTEM_REMINDER, TOOL_CALL_DISABLED_MESSAGE } from './btw'; +import { + BTW_READONLY_TOOLS, + ISessionBtwService, + SIDE_QUESTION_SYSTEM_REMINDER, + TOOL_CALL_DISABLED_MESSAGE, +} from './btw'; export class SessionBtwService implements ISessionBtwService { declare readonly _serviceBrand: undefined; @@ -32,7 +37,9 @@ export class SessionBtwService implements ISessionBtwService { child.accessor .get(IAgentToolExecutorService) ?.onBeforeExecuteTool((event) => { - event.veto(denyToolExecution(reason)); + if (!BTW_READONLY_TOOLS.has(event.toolCall.name)) { + event.veto(denyToolExecution(reason)); + } }); return childContext.agentId; } diff --git a/packages/agent-core-v2/test/features/btw/btw.test.ts b/packages/agent-core-v2/test/features/btw/btw.test.ts index 8f45dcbc477..c83b098267b 100644 --- a/packages/agent-core-v2/test/features/btw/btw.test.ts +++ b/packages/agent-core-v2/test/features/btw/btw.test.ts @@ -7,6 +7,7 @@ import { TestInstantiationService } from '#/_base/di/test'; import { IAgentToolApprovalService } from '#/agent/toolApproval/toolApproval'; import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor'; import { + BTW_READONLY_TOOLS, ISessionBtwService, SIDE_QUESTION_SYSTEM_REMINDER, TOOL_CALL_DISABLED_MESSAGE, @@ -86,26 +87,47 @@ describe('SessionBtwService', () => { }); }); - it('vetoes every tool call on the child through the btw deny listener', async () => { + it('vetoes non-read-only tool calls on the child through the btw deny listener', async () => { const svc = ix.get(ISessionBtwService); await svc.start(); - const toolCall: ToolCall = { type: 'function', id: 'call_1', name: 'Bash', arguments: '{}' }; - const decision = await executorEvents.fireBeforeExecute({ - turnId: 0, - signal: new AbortController().signal, - toolCall, - toolCalls: [toolCall], - args: {}, - execution: { approvalRule: 'Bash', execute: async () => ({ output: '' }) }, - }); + for (const name of ['Bash', 'Write', 'Edit']) { + const toolCall: ToolCall = { type: 'function', id: `call_${name}`, name, arguments: '{}' }; + const decision = await executorEvents.fireBeforeExecute({ + turnId: 0, + signal: new AbortController().signal, + toolCall, + toolCalls: [toolCall], + args: {}, + execution: { approvalRule: name, execute: async () => ({ output: '' }) }, + }); - expect(decision).toEqual({ - veto: { - output: `${TOOL_CALL_DISABLED_MESSAGE} [worker guidance]`, - isError: true, - }, - }); + expect(decision).toEqual({ + veto: { + output: `${TOOL_CALL_DISABLED_MESSAGE} [worker guidance]`, + isError: true, + }, + }); + } expect(formatDenyMessage).toHaveBeenCalledWith(TOOL_CALL_DISABLED_MESSAGE); }); + + it('allows read-only tool calls (Read, Grep, Glob) on the child', async () => { + const svc = ix.get(ISessionBtwService); + await svc.start(); + + for (const name of BTW_READONLY_TOOLS) { + const toolCall: ToolCall = { type: 'function', id: `call_${name}`, name, arguments: '{}' }; + const decision = await executorEvents.fireBeforeExecute({ + turnId: 0, + signal: new AbortController().signal, + toolCall, + toolCalls: [toolCall], + args: {}, + execution: { approvalRule: name, execute: async () => ({ output: '' }) }, + }); + + expect(decision).toBeUndefined(); + } + }); }); From ef918b3da831ce0871afdf049187d08308670f8c Mon Sep 17 00:00:00 2001 From: liukx0205 <69756503+liukx0205@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:56:29 +0000 Subject: [PATCH 2/3] =?UTF-8?q?docs:=20address=20review=20=E2=80=94=20sing?= =?UTF-8?q?le-sentence=20changeset,=20inline-code=20tool=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/btw-readonly-tools.md | 2 +- docs/en/reference/server-api.md | 2 +- docs/zh/reference/server-api.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/btw-readonly-tools.md b/.changeset/btw-readonly-tools.md index 05e0478e050..3f85ec8e172 100644 --- a/.changeset/btw-readonly-tools.md +++ b/.changeset/btw-readonly-tools.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": minor --- -Add read-only tools to the /btw side agent. It can now read and search files to answer questions about the codebase; write and execute tools stay disabled. +Add read-only tools to the /btw side agent so it can answer questions about files in the codebase. diff --git a/docs/en/reference/server-api.md b/docs/en/reference/server-api.md index 3fbb866d100..f38aa633d7a 100644 --- a/docs/en/reference/server-api.md +++ b/docs/en/reference/server-api.md @@ -751,7 +751,7 @@ On success, `data` is `{ aborted: true }`. #### `POST /api/v1/sessions/{session_id}:btw` -Starts a "by the way" side conversation: forks the main agent into a child agent whose tool calls are limited to the read-only tools Read, Grep, and Glob, so quick side questions run in isolation without touching the working context. Requires a usable model configuration. +Starts a "by the way" side conversation: forks the main agent into a child agent whose tool calls are limited to the read-only tools `Read`, `Grep`, and `Glob`, so quick side questions run in isolation without touching the working context. Requires a usable model configuration. On success, `data` is `{ agent_id }` — the id of the new child agent. diff --git a/docs/zh/reference/server-api.md b/docs/zh/reference/server-api.md index ee8b250fc8e..530dfd3cb4b 100644 --- a/docs/zh/reference/server-api.md +++ b/docs/zh/reference/server-api.md @@ -751,7 +751,7 @@ schema 还接受 `agent_config` 内的 `system_prompt`、`tools`、`mcp_servers` #### `POST /api/v1/sessions/{session_id}:btw` -开启一个 `"by the way"` 旁路对话:把 main agent fork 成一个仅可使用只读工具(Read、Grep、Glob)的子 Agent,让快速的临时问题在隔离环境中运行,不触碰工作上下文。需要可用的模型配置。 +开启一个 `"by the way"` 旁路对话:把 main agent fork 成一个仅可使用只读工具(`Read`、`Grep`、`Glob`)的子 Agent,让快速的临时问题在隔离环境中运行,不触碰工作上下文。需要可用的模型配置。 成功时,`data` 为 `{ agent_id }`——新子 Agent 的 id。 From 275a264be47b5cb5c04cd2aeb275c1aefc27a249 Mon Sep 17 00:00:00 2001 From: liukx0205 <69756503+liukx0205@users.noreply.github.com> Date: Mon, 7 Sep 2026 10:12:54 +0000 Subject: [PATCH 3/3] chore: drop the benefit clause from the btw changeset --- .changeset/btw-readonly-tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/btw-readonly-tools.md b/.changeset/btw-readonly-tools.md index 3f85ec8e172..52e43d02e91 100644 --- a/.changeset/btw-readonly-tools.md +++ b/.changeset/btw-readonly-tools.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": minor --- -Add read-only tools to the /btw side agent so it can answer questions about files in the codebase. +Add read-only tools to the /btw side agent.