Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/monitor-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Add a `Monitor` tool: run a self-filtering shell command in the background and receive each new stdout line as a notification (with batching and an auto-stop volume cap). Stop with `TaskStop` or let it time out; `persistent` watches run until the session ends.
5 changes: 4 additions & 1 deletion docs/en/reference/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,23 @@ Collaboration tools handle inter-Agent coordination, user interaction, and Skill

## Background Tasks

Background task tools manage tasks started via `Bash`, `Agent`, or `AskUserQuestion`. When a task reaches a terminal state, its status and saved output path are automatically delivered back to the Agent; use `TaskOutput` to check progress early.
Background task tools manage tasks started via `Bash`, `Agent`, `AskUserQuestion`, or `Monitor`. When a task reaches a terminal state, its status and saved output path are automatically delivered back to the Agent; use `TaskOutput` to check progress early.

| Tool | Default Approval | Description |
| --- | --- | --- |
| `TaskList` | Auto-allow | List background tasks |
| `TaskOutput` | Auto-allow | View the output of a background task |
| `TaskStop` | Requires approval | Stop a running background task |
| `Monitor` | Requires approval | Run a self-filtering shell command and receive each stdout line as a notification |

**`TaskList`** returns the list of background tasks. Optional parameters: `active_only` (defaults to true; lists only running tasks) and `limit` (defaults to 20; range 1–100).

**`TaskOutput`** returns the status and output of a task given its `task_id`. The inline preview includes at most the most recent 32 KB of content; the full log is saved to disk, and the tool also returns an `output_path` with a suggestion to use `Read` for paginated access. Optional `block` (defaults to false) and `timeout` (seconds to wait; defaults to 30; range 0–3600) parameters allow waiting for the task to complete before returning.

**`TaskStop`** accepts a `task_id` and optional `reason` (defaults to `Stopped by TaskStop`). Safe to call on tasks that are already in a terminal state.

**`Monitor`** runs a self-filtering shell command in the background and delivers each matching stdout line as a real-time `<notification type="monitor_line">`. Parameters: `command` (required; prefer line-buffered filters such as `grep --line-buffered` or `awk` with `fflush()`), `description` (shown in every notification), `timeout_ms` (defaults to 5 minutes; ignored when `persistent=true`), and `persistent` (run until the session ends or `TaskStop` is called). Output is batched over a short window and auto-stopped if it exceeds 200 lines per 5-second window; tighten the filter and restart if this happens.

## Scheduled Tasks

Scheduled task tools allow the Agent to re-inject a prompt into the current session at a future time — either as a one-time reminder or as a recurring cron-triggered task (periodic checks, daily reports, deployment monitoring, etc.). Schedules are bound to the session and remain active after `kimi resume`, but are not carried into a brand-new session. A single session can hold at most 50 active scheduled tasks. Set `KIMI_DISABLE_CRON=1` to disable them entirely; see [Environment Variables](../configuration/env-vars.md#运行时开关).
Expand Down
5 changes: 4 additions & 1 deletion docs/zh/reference/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,23 @@ Plan 模式是一种受约束的工作状态:进入后 `Write` 与 `Edit` 只

## 后台任务

后台任务工具用于管理通过 `Bash`、`Agent` 或 `AskUserQuestion` 启动的后台任务。任务进入终止状态时会自动把状态和已保存的输出路径送回 Agent;如需提前检查进度,使用 `TaskOutput`。
后台任务工具用于管理通过 `Bash`、`Agent`、`AskUserQuestion` 或 `Monitor` 启动的后台任务。任务进入终止状态时会自动把状态和已保存的输出路径送回 Agent;如需提前检查进度,使用 `TaskOutput`。

| 工具 | 默认审批 | 说明 |
| --- | --- | --- |
| `TaskList` | 自动放行 | 列出后台任务 |
| `TaskOutput` | 自动放行 | 查看后台任务的输出 |
| `TaskStop` | 需审批 | 停止正在运行的后台任务 |
| `Monitor` | 需审批 | 运行自过滤 shell 命令,将每条 stdout 作为通知推送 |

**`TaskList`** 返回后台任务列表。可选参数 `active_only`(默认 true,仅列出运行中的任务)和 `limit`(默认 20,取值范围 1–100)。

**`TaskOutput`** 根据 `task_id` 返回任务状态与输出。内联预览最多包含最近 32 KB 的内容;完整日志保存在磁盘上,工具会一并返回 `output_path` 并提示通过 `Read` 分页读取。可选 `block`(默认 false)和 `timeout`(等待秒数,默认 30,取值范围 0–3600)参数可用于等待任务完成后再返回。

**`TaskStop`** 接受 `task_id` 和可选的 `reason`(默认 `Stopped by TaskStop`)。对已处于终止状态的任务也能安全调用。

**`Monitor`** 在后台运行一条自过滤的 shell 命令,将每条匹配的 stdout 行作为实时的 `<notification type="monitor_line">` 推送。参数:`command`(必填;建议使用行缓冲过滤器,如 `grep --line-buffered` 或带 `fflush()` 的 `awk`)、`description`(每条通知中显示)、`timeout_ms`(默认 5 分钟;`persistent=true` 时忽略)、`persistent`(持续到会话结束或调用 `TaskStop`)。输出会在一个短窗口内批量推送,若 5 秒内超过 200 行会自动停止;出现这种情况时请收紧过滤条件后重新启动。

## 定时任务

定时任务工具允许 Agent 把一段 prompt 在未来某个时间重新注入到当前会话——既可以是一次性提醒,也可以是按 cron 周期触发的任务(定期巡检、每日报表、部署监控等)。计划绑定到会话,执行 `kimi resume` 后仍然有效,但不会带入全新的会话。单个会话最多保留 50 个生效中的定时任务。设置 `KIMI_DISABLE_CRON=1` 可整体禁用,详见[环境变量](../configuration/env-vars.md#运行时开关)。
Expand Down
61 changes: 60 additions & 1 deletion packages/agent-core/src/agent/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { randomBytes } from 'node:crypto';

import { createControlledPromise, type ControlledPromise } from '@antfu/utils';
import type { ContentPart } from '@moonshot-ai/kosong';
import type { KaosProcess } from '@moonshot-ai/kaos';

import type { Agent } from '../..';
import { errorMessage } from '../../loop/errors';
Expand All @@ -22,6 +23,8 @@ import { escapeXml, escapeXmlAttr } from '../../utils/xml-escape';
import type { BackgroundTaskOrigin } from '../context';
import { renderNotificationXml } from '../context/notification-xml';
import { type BackgroundTaskPersistence } from './persist';
import { MonitorBackgroundTask } from './monitor-task';
import type { MonitorEmit } from './monitor-task';
import {
TERMINAL_STATUSES,
type BackgroundTask,
Expand All @@ -48,6 +51,8 @@ export { ProcessBackgroundTask } from './process-task';
export type { ProcessBackgroundTaskInfo } from './process-task';
export { QuestionBackgroundTask } from './question-task';
export type { QuestionBackgroundTaskInfo } from './question-task';
export { MonitorBackgroundTask } from './monitor-task';
export type { MonitorBackgroundTaskInfo } from './task';
export { BackgroundTaskPersistence } from './persist';
export type {
BackgroundTaskInfo,
Expand Down Expand Up @@ -187,7 +192,7 @@ type BackgroundTaskNotification = Record<string, unknown> & {
readonly id: string;
readonly category: 'task';
readonly type: string;
readonly source_kind: 'background_task';
readonly source_kind: 'background_task' | 'monitor';
readonly source_id: string;
/** Subagent id accepted by Agent(resume=...). Omitted for process tasks. */
readonly agent_id?: string | undefined;
Expand Down Expand Up @@ -252,6 +257,7 @@ export class BackgroundManager {

private readonly scheduledNotificationKeys = new Set<string>();
private readonly deliveredNotificationKeys = new Set<string>();
private readonly monitorSeqCounters = new Map<string, number>();

constructor(
private readonly agent: Agent,
Expand Down Expand Up @@ -313,6 +319,24 @@ export class BackgroundManager {
return entry.options.autoBackgroundOnTimeout === true && !this.isDetached(entry);
}

registerMonitorTask(
proc: KaosProcess,
command: string,
description: string,
options: RegisterBackgroundTaskOptions = {},
): string {
const taskIdHolder: { taskId: string } = { taskId: '' };
const emit: MonitorEmit = (lines, severity) => {
this.monitorNotify(taskIdHolder.taskId, description, lines, severity);
};
const taskId = this.registerTask(
new MonitorBackgroundTask(proc, command, description, emit, {}, options.timeoutMs),
options,
);
taskIdHolder.taskId = taskId;
return taskId;
}

registerTask(task: BackgroundTask, options: RegisterBackgroundTaskOptions = {}): string {
const detached = options.detached ?? true;
const timeoutMs = options.timeoutMs ?? task.timeoutMs;
Expand Down Expand Up @@ -777,6 +801,41 @@ export class BackgroundManager {
this.fireNotificationHook(context.notification);
}

private nextMonitorSeq(taskId: string): number {
const next = (this.monitorSeqCounters.get(taskId) ?? 0) + 1;
this.monitorSeqCounters.set(taskId, next);
return next;
}

private monitorNotify(
taskId: string,
description: string,
lines: string[],
severity: 'info' | 'warning' = 'info',
): void {
const body = lines.join('\n');
const notification: BackgroundTaskNotification = {
id: `monitor:${taskId}:${this.nextMonitorSeq(taskId)}`,
category: 'task',
type: 'monitor_line',
source_kind: 'monitor',
source_id: taskId,
title: description,
severity,
body,
tail_output: '',
};
const text = renderNotificationXml(notification);
const origin: BackgroundTaskOrigin = {
kind: 'background_task',
taskId,
status: 'running',
notificationId: `monitor:${taskId}`,
};
this.agent.turn.steer([{ type: 'text', text }], origin);
this.fireNotificationHook(notification);
}

private async restoreBackgroundTaskNotification(info: BackgroundTaskInfo): Promise<void> {
const context = await this.buildBackgroundTaskNotificationContext(info);
if (context === undefined) return;
Expand Down
Loading