KAIROS 常驻助手:Claude Code 源码中隐藏的主动系统
5 分钟阅读
如果你用过 Claude Code,你会发现它有个很独特的体验:有时你正看着终端发呆,它突然弹出一行提示——“我在检查 CI 状态……发现了 3 个失败的测试”。你没有发任何指令,没有敲任何命令,它自己决定要做点什么。
这不是幻觉。这是 Claude Code 源码中一个隐藏至深的系统——KAIROS 常驻助手。
在拆解完 Multi-Agent 机制(#1)和 工具系统架构(#2)之后,今天这一篇终于触及 Claude Code 源码中最大胆的设计:一个不依赖用户输入、自我驱动的常驻 AI 助手。
它不是一般的 Agent 轮询模式。它不等待用户说"去做 X",而是主动观察环境、做出判断、低调行动、只在有价值时才出声。KAIROS 不是一个实验性功能——它是 Claude Code 源码中由编译开关保护、从系统提示词到工具集完全独立的一套平行系统。

一、背景:为什么 AI 需要"常驻"而不是"一次性召唤"
从"命令-响应"到"观察-行动"
Claude Code 的常规模式本质上是一套"命令-响应"系统:用户输入一句话,Agent 思考、调用工具、给结果——然后等待下一个输入。
这套模式在任何聊天驱动的 AI 产品中都够用。但在终端里,你有一层额外的环境:文件在变化、日志在写入、CI 在运行、Git 分支在切换。这些变化不会主动通知你——你得自己去翻。
KAIROS 的出发点很朴素:AI 不应该等你问才知道该做什么,它应该现在就坐在你旁边看着。
这种隐喻不是修辞,而是精确的设计取舍。它不是"用户的分身"——它是"坐在旁边看的人"。这意味着:
- 它有自己的观察视角,不替代用户决策
- 它只在看见值得说的事情时才开口
- 用户忙的时候它自动安静
KAIROS 为何被编译开关保护
KAIROS 位于 assistant/ 目录,由 PROACTIVE / KAIROS 编译期开关控制。外部发布版完全不包含这段代码。这意味着 Anthropic 自己也在谨慎地测试这个功能,尚未决定是否开放给所有用户。
一个被动的 Agent 只在收到指令时行动,你清楚它做了什么——因为是你让它做的。一个主动的 Agent 可能在你没注意的时候改了不该改的文件、发了不该发的消息、读了不该读的信息。KAIROS 的整个体系设计,就是在回答一个问题:如何让一个主动的 AI 既强大又安全?
二、KAIROS 的 Tick 驱动机制
从用户输入到系统节拍
常规 Agent 的工作循环由用户消息触发:
用户输入 → 模型推理 → 工具调用 → 输出 → 等待下一个用户输入
KAIROS 换掉了触发器:不再等待用户输入,而是按固定时间间隔接收系统级 Tick 信号。
系统 Tick → KAIROS 自检 → 决策是否行动 → 行动(或静默) → 等待下一个 Tick

Tick 的执行语义
这个 Tick 不是 JavaScript 的 setInterval——它比那复杂得多。Tick 是一个由系统注入的结构化信号,携带当前环境快照:
interface TickContext {
timestamp: number; // 当前时间
delta: number; // 距上次 Tick 的时间差
pendingChanges: Change[]; // 待处理的环境变更
userActive: boolean; // 用户是否正在交互
blockingBudget: number; // 当前可用的阻塞预算是多少毫秒
}
每次 Tick 到达时,KAIROS 会:
- 1检查优先级——对比当前观察队列中各项事务的紧急程度
- 2检查阻塞预算——如果某个动作会阻塞用户操作 >15 秒,自动延后
- 3检查用户活跃度——用户正在终端输入时,不发声
- 4决定输出模式——用完整输出还是 Brief 模式
日志系统:Tick 的反馈回路
KAIROS 维护着一套按天追加写入的日志系统:
// 日志路径示例:~/.claude/kairos/logs/2026-05-26.md
// 每条日志格式:[HH:mm:ss] <观察事件> → <决策> → <动作结果>
日志持续记录:
- 每个 Tick 触发了哪些观察
- 做了哪些决策
- 执行了什么动作
- 动作的结果
这套日志是 KAIROS 的"外部记忆"——不依赖模型上下文来回顾自己的行为。这对于一个自主行动的 Agent 至关重要:没有日志,它不知道自己半小时前做了什么;有了日志,它能基于真实历史做决策,而不是靠模糊的上下文推理。
三、15 秒阻塞预算的设计哲学
为什么是 15 秒?
在终端工作的核心体验是"我在控制"。用户敲下命令、看到输出、做下一步决策——这是一个循环。
KAIROS 如果花 30 秒分析日志、然后弹结果,用户要么等得不耐烦,要么以为自己把终端搞死了。但如果它只花 5 秒看一眼就退回来——就像余光扫过——用户几乎感知不到。
阻塞预算的分配策略
const BLOCKING_BUDGET_MS = 15_000;
function canProceed(context: TickContext): boolean {
if (context.blockingBudget < BLOCKING_BUDGET_MS) return false;
// 如果动作预估耗时 > 剩余预算,延后
if (estimatedDuration(context.pendingAction) > context.blockingBudget) {
deferToNextTick(context.pendingAction);
return false;
}
return true;
}
阻塞预算的分配遵循几条原则:
- 1短动作优先:低于 1 秒的动作(查文件是否存在、读一行日志)几乎总是放行
- 2长动作排队:超过 5 秒的动作需要额外判断价值
- 3超时动作被暂停:如果一个动作开始后阻塞了 >15 秒还未完成,KAIROS 应该主动中止它

如果 AI 的主动行为挡到了你——不管它觉得那事多重要——它应该让路。这个 15 秒不仅仅是技术参数,它是"用户优先"从口头承诺到工程落地的代码化表达。
四、Brief 极简输出模式
从话痨到惜字如金
常规 Claude Code 的输出是完整的:它解释在做什么、怎么做、结果是什么、下一步建议是什么——如同一个热心的同事。
但 KAIROS 的输出不是这样。
它的输出模式叫 Brief——极度简洁的轻量输出风格。目标是:在用户不被集中注意力的前提下传递最大信息量。
一个带 Brief 和常规输出对比的例子:
常规输出(用户主动发起):
"I've checked the CI status for the last 3 runs. The latest run (ID #1423) has 2 failing
tests in the auth module. Here's the error: AssertionError at fixtures/test_auth_flow.py:84.
The issue appears to be caused by a recent change to the JWT token expiry in PR #442.
Would you like me to investigate further?"
Brief 输出(KAIROS 主动推送):
"[CI: auth 模块有 2 个测试失败,JWT 过期变更相关 #442]"
一行。没有上下文铺垫,没有解释步骤,没有后续建议。用户知道发生了什么、相关的 PR 是什么——然后自己去决定要不要深入。
Brief 的触发条件
function shouldUseBrief(output: ActionOutput): boolean {
// 用户不在交互状态 → 用 Brief
if (!context.userActive) return true;
// 正在处理紧急问题 → 用完整输出
if (context.urgentIssue) return false;
// 信息量低于阈值 → 用 Brief(不值得细说)
if (output.importance < BRIEF_THRESHOLD) {
return true; // 或用 HEARTBEAT_OK 完全静默
}
return false;
}
KAIROS 的输出风格不是"节省 token"——那是表面效果。深层目的是尊重用户的注意力带宽。每个终端开发者都经历过"日志刷屏"的痛苦——KAIROS 的 Brief 模式从根本上避免成为那个刷屏者。

五、KAIROS 专属工具
普通 Agent 没有的能力
KAIROS 的工具箱里,有几个普通 Claude Code 完全不存在的工具。这些工具的设计让 KAIROS 能做的不仅仅是"看"——它还能主动推送。
SendUserFile:直接推文件
普通 Agent 写文件,是你告诉它"把结果写到这里"。KAIROS 的 SendUserFile 能直接把文件推到用户设备上——用户甚至不需要知道文件存在,KAIROS 自己决定推送。
interface SendUserFileInput {
path: string; // 本地文件路径
reason: string; // 为什么推这个文件
format?: 'markdown' | 'raw' | 'diff';
}
PushNotification:设备推送
这是最能体现"常驻"属性的工具。KAIROS 可以向用户设备发送推送通知——不是终端里的气泡,是手机弹出的通知。
场景:你合上了笔记本去喝咖啡,CI 终于跑完了。KAIROS 发一条推送——“CI 通过了 ✅"。你不需要一直盯着终端。
interface PushNotificationInput {
title: string; // 推送标题(极限简洁)
body: string; // 推送正文(控制在 100 字内)
priority?: 'info' | 'warning' | 'urgent';
}
SubscribePR:Pull Request 订阅监控
KAIROS 不只在当前 Tick 扫描一次 PR——它订阅 PR 的变化事件,持续监控。当有人 review、有新 comment、CI 状态变化时,它主动接收变更并采取行动。
interface SubscribePRInput {
repo: string;
prNumber: number;
events: ('review' | 'comment' | 'status' | 'merge')[];
actionOnEvent?: 'notify' | 'fetch-diff' | 'check-status';
}

为什么这些工具"不能"给普通 Agent
如果普通 Claude Code 也有这些工具:
- SendUserFile:用户让 Agent 写个 Hello World,Agent 决定推 100 个文件到用户桌面
- PushNotification:Agent 在深更半夜发 50 条通知告诉你任务完成
- SubscribePR:Agent 订阅了 200 个 PR,把消息队列撑爆
六、autoDream 记忆整合引擎
为什么要有一个独立的记忆系统?
在 #2 中我们拆解了 Claude Code 的工具系统——40 个工具各司其职,包括读写文件。但有一个更深层的需求是"读写文件"无法满足的:Agent 需要定期停下来,思考自己学到了什么。
这就是 autoDream 记忆整合引擎的定位——位于 services/autoDream/ 目录。
三重门槛机制
autoDream 不是每次空闲都跑。它有三道门槛把关:
const DREAM_GUARD = {
timeThreshold: 24 * 60 * 60 * 1000, // 24h 时间门槛
sessionThreshold: 5, // 5 次会话门槛
lockThreshold: { maxConcurrent: 1 }, // 防并发锁
};
function shouldDream(context: DreamContext): boolean {
// 门一:距离上次 dream 不足 24h → 不跑
if (Date.now() - context.lastDreamTime < DREAM_GUARD.timeThreshold) return false;
// 门二:距离上次 dream 不足 5 次会话 → 不跑
if (context.sessionsSinceLastDream < DREAM_GUARD.sessionThreshold) return false;
// 门三:另一个 dream 正在跑 → 不跑(防并发)
if (context.dreamInProgress) return false;
return true;
}
四阶段流程
当三重门槛全部通过后,autoDream 启动四阶段流程:
type DreamStage = 'Orient' | 'Gather' | 'Consolidate' | 'Prune';
async function runDream(context: DreamContext): Promise<DreamResult> {
const state: DreamState = { ... };
// 1. Orient:快速扫描当前知识状态,标记过时/冲突的记忆
state.stage = 'Orient';
const scanResult = await scanMemory(state.memoryIndex);
// 2. Gather:从会话日志、工具调用记录、输出产物中提取新信息
state.stage = 'Gather';
const newInsights = await gatherInsights(context.sessionLogs);
// 3. Consolidate:综合新旧信息,更新记忆结构
state.stage = 'Consolidate';
const consolidated = await consolidate(scanResult, newInsights);
// 4. Prune:裁剪过时/冗余的记忆,保持记忆质量
state.stage = 'Prune';
const pruned = await prune(consolidated, {
maxTokens: MEMORY_BUDGET,
});
return { ...pruned, summary: 'Dream completed' };
}
每阶段之间有明确的检查点,任何一个阶段耗尽了资源预算就提前结束——宁可不做完,也不要强制占用系统资源。

只读 bash 权限的特殊设计
autoDream 在运行时的 bash 权限被严格限制为 只读。它可以用 bash 读日志文件、查 git 历史、看系统状态——但不能写任何东西。
KAIROS + autoDream:主动系统中的互补组件
KAIROS 负责"向外看”——观察终端、作业、PR 等外部状态,决定何时该行动。autoDream 负责"向内看"——观察 Agent 自己的记忆和经历,决定何时该整理。两个系统一个向外一个向内,构成了完整的主动决策循环。
七、三条设计原则
从 KAIROS 和 autoDream 的源码设计中,可以提炼出三条适用于任何主动 Agent 系统的设计原则。
原则 1:帮助但不打扰(Helpful but Not Annoying)
这条写在 KAIROS 的系统提示词里,但更重要的是它写在了每个工程决策中:
- 15 秒阻塞预算——如果会挡住你,那就不做
- Brief 输出模式——如果不说也行,那就不说
- Tick 节拍检查用户活跃度——如果你在忙,那就不打扰
- autoDream 的三重门槛——如果不够格,那就不跑
衡量一个主动系统是否成熟,不看你让它多能干,而看它多能控制自己。
原则 2:低优先级是美德
主动系统的核心悖论是:它越主动,越可能干扰用户。KAIROS 和 autoDream 共用一套降级策略:
- KAIROS:长任务延后到下次 Tick,阻塞超预算就放弃
- autoDream:三重门槛防过早执行,阶段预算用完就提前结束
主动系统的"主动"不是"抢占"——恰恰相反,主动系统应该把自己当成最低优先级的任务,永远给用户的主线程让路。
原则 3:价值优先于频率
KAIROS 不追求每次 Tick 都有输出。实际上,大部分 Tick 的决策结果都是"什么都不做"。
# 日志示例:一个典型的周一早晨
[07:32:15] Tick: 扫描文件变更 → 无可议项目 → 静默
[07:37:14] Tick: 扫描文件变更 → 无可议项目 → 静默
[07:42:16] Tick: CI 状态变化 → 3 个测试失败 → Brief 推送
[07:47:13] Tick: 扫描 CI 修复进度 → PR #442 已打开修正 → 静默(用户已知)
[07:52:15] Tick: 无可议项目 → 静默
5 个 Tick,只有 1 次真正输出了有价值信息。这不是低效——这就是设计目标。
写在最后
- Multi-Agent 解决的是规模和隔离——多个 Agent 一起干活不乱
- 工具系统 解决的是边界和安全——Agent 能做什么、不能做什么
- KAIROS 解决的是主动性和克制——AI 如何在无指令时仍创造价值
前两个问题的答案已经比较清晰——隔离、分级、缓存、消息驱动——这是许多 Agent 框架正在追赶的方向。但 KAIROS 揭示了第三个问题的答案,而这是目前市面上几乎没有任何产品认真解决的问题:一个不依赖用户输入的 AI 助手,应该如何设计?
KAIROS 的回答是:Tick 驱动但不抢占、观察但不唠叨、行动但不越界、只在有价值时出声。
这不是一个"实验"或"功能开关"——这是一个新范式的原型。当所有 Agent 都在追逐"更快响应用户"时,KAIROS 追问了一个相反的问题:“当用户不说话时,我们应该做什么?”
下一篇预告:Fork Subagent & Prompt Cache——把成本降到 10% 的工程智慧。 当 Multi-Agent 把计算分散到多台机器时,Anthropic 做了一个反直觉的设计——getSystemPrompt() 返回空字符串。这不是 bug,而是一场围绕 Prompt Cache 字节级对齐的极致成本博弈。