对最近 AI 落地工程实践的一些想法和思考

最近和小区某上市公司的 CFO 喝茶聊 AI,在过程中思维和实际场景的碰撞,记录如下:

穿透复杂的表象,当前 LLM 的底层运行逻辑其实非常单一:它本质上是一个自回归的序列生成器,根据已有的上下文,计算词表中每一个 token 出现的概率分布,然后从中采样出下一个 token。

但这里的「概率」绝非毫无逻辑的随机掷骰子。 这种概率分布,是模型在海量预训练数据中内化的语言规律、世界知识以及逻辑推理能力的数学投影。通过多层 Transformer 网络与注意力机制(Attention),模型在极高的维度上完成了对上下文语义的深度解析与特征关联,从而将符合人类逻辑、契合当前语境的 token 赋予极高的概率权重。它是在用统计学的方式,重现人类的逻辑推理过程。

然而,无论其内部的概率计算多么精密,从软件工程的宏观视角来看,我们本质上依然是在传统的确定性系统中,强行引入了一个基于概率采样的非确定性组件。

传统软件工程建立在严格的确定性之上。输入特定的参数,经过固定的业务逻辑,必然得到预期的输出。现在我们将核心逻辑交由概率模型处理,相同的输入在不同的时间点,可能会产生完全不同的输出路径。

幻觉无法被根除。它是自回归模型的内生特性,是概率采样的必然产物。我们在进行系统架构设计时,必须将幻觉视为系统的常态。试图通过修改 Prompt 来彻底消除幻觉,在工程上徒劳无功。我们需要在系统边界处建立起拦截机制,用确定性的规则去兜底概率模型的不确定性。

容错度决定落地

当前商业化落地最顺畅、ROI 最高的场景,全部集中在高容错度领域。写行业报告、生成营销文案、文生图、视频生成、游戏 NPC 对话。这类场景的核心特征在于缺乏绝对的客观标准。

在内容创作领域,模型偶尔的逻辑发散会被用户视为创造力。工程团队不需要在接口的绝对可用性和输出的绝对准确性上死磕,只需要保证底线的内容安全和合理的响应延迟。系统可用性达到 95% 就能让用户产生极强的获得感。

一旦进入低容错度场景,工程实现的复杂度会呈指数级上升。医疗诊断、工业控制、核心交易链路。在这些领域,0.1% 的幻觉率都会导致灾难性的业务后果。我们在评估一个 AI 项目是否立项时,首要考量指标就是业务场景的容错底线。容错度越低,外围需要的确定性校验代码就越厚重,最终会导致系统的维护成本远超 AI 带来的效率提升。

知识外挂 RAG

RAG 的出现是为了解决模型内部知识更新滞后和私有数据隔离的问题。其核心原理是将外部文档切片、向量化,在用户提问时检索相关切片,拼接到 Prompt 中作为上下文喂给大模型。

在实际的工程环境里,RAG 的核心瓶颈在检索链路。切片策略直接决定了召回质量。按固定 token 长度切分会破坏语义完整性,导致关键信息被腰斩。按标点符号或段落切分会导致切片长度方差过大,影响向量化模型的表达能力。我们在生产环境中通常需要针对不同格式的文档编写定制化的解析器,将 PDF 或 Word 还原为结构化的文档树,再基于文档树的层级进行语义切片。

单一的向量检索在面对专有名词和长尾词汇时表现极差。我们必须采用混合检索架构:稠密向量检索加上稀疏词表检索。向量检索负责语义泛化,处理同义词和模糊表达。词表检索负责精准匹配产品型号、人名和内部项目代号。混合检索引入了多路召回合并的问题,通常需要引入倒数秩融合算法来重排结果。系统复杂度和查询延迟会成倍增加。

数据清洗占据了 RAG 项目 80% 的研发精力。直接将企业内部的原始文档灌入向量数据库,最终的问答准确率通常不到 40%。文档中存在大量的废话、过期的流程规范以及相互冲突的条款。垃圾进,垃圾出。我们在构建知识库之前,必须通过脚本和人工介入,对语料进行严格的去重、降噪和结构化提取。

工具调用确定性

为了弥补概率模型的缺陷,我们需要引入确定性的工具。Function Calling 机制本质上是给 LLM 接上双手。模型负责理解自然语言意图并提取结构化参数,具体的业务逻辑交由传统的确定性脚本执行。

工具调用的工程难点在于参数提取的稳定性。当注册的工具数量超过十个,或者参数结构嵌套层级过深时,模型的输出格式极易崩溃。我们在中间层必须加入严格的 Schema 校验机制。一旦校验失败,需要截断错误信息并触发重试。重试次数上限通常设定为 3 次,继续增加会耗尽上下文窗口并导致请求超时。

多轮工具调用会带来严重的延迟问题。模型每决定调用一次工具,都需要经历一次完整的网络请求和推理过程。如果一个复杂任务需要串行调用三个工具,用户的等待时间会轻易突破 10 秒。我们在架构设计时,需要尽可能将细粒度的 API 聚合成粗粒度的宏接口,减少模型与业务系统的交互频次

Agent 架构的脆弱性与状态管理

多智能体(Multi-Agent)架构在技术社区被过度神话。多个大模型相互协作、自主规划任务的 Demo 看起来非常惊艳。在真实的工业场景中,完全由 LLM 自主驱动的 Agent 链路极其脆弱。

误差会在多步推理中被迅速放大。假设单个 Agent 节点的输出准确率为 90%,一个包含五个节点的串行任务,最终的成功率会暴跌至 59%。任何一个节点的幻觉都会导致后续链路彻底跑偏。

我们在生产环境中构建复杂任务流时,坚决摒弃由 LLM 自主决定执行路径的黑盒模式。控制流必须由传统的有向无环图(DAG)或状态机来接管。LLM 仅仅作为状态机中的一个计算节点,负责处理非结构化数据的理解和生成。节点与节点之间的状态流转、条件判断、异常重试,全部由确定性的代码实现。这种设计牺牲了系统的灵活性,换取了业务系统必须具备的稳定性和可观测性。

非确定性系统的测试与监控

非确定性系统的测试与监控,是传统软件工程团队转型 AI 开发时遇到的最大痛点。传统的单元测试基于断言,期望输出是固定的字符串或数值。面对 LLM 每次都不一样的回答,基于精确匹配的 CI/CD 流水线会全线崩溃。

我们重构了整个测试评估体系。引入 LLM-as-a-Judge 机制,使用一个能力更强、参数规模更大的模型来评估业务模型的输出质量。评估维度被拆解为相关性、事实一致性、格式合规性等具体指标。在每次模型版本迭代或 Prompt 修改后,必须在包含上千个真实业务 Case 的黄金数据集上运行自动化评估。只有各项指标的波动在可控范围内,才能进行灰度发布。

在监控层面,传统的 APM 工具无法满足需求。我们需要采集每一个请求的 Prompt 模板版本、输入变量、输出结果、Token 消耗量以及推理延迟。这些数据是后续进行 Bad Case 分析和模型微调的唯一原料。针对 Token 消耗的监控直接与业务成本挂钩。我们会在网关层设置严格的并发限制和预算熔断机制,防止恶意请求或死循环调用导致账单失控。

两种范式的碰撞

AI First 与 AI 辅助是完全不同的架构逻辑。

AI 辅助是在现有系统中打补丁。主干流程依然是传统的表单和按钮,AI 作为一个侧边栏或悬浮窗存在,提供总结、翻译、润色功能。开发成本极低,对原有系统无侵入。用户在遇到问题时,可以选择性地向 AI 求助。

AI First 要求重构整个交互形态和底层流转逻辑。系统不再依赖预设的菜单树,由 LLM 充当中央路由。用户的自然语言输入直接驱动底层状态机流转。这要求所有内部 API 具备极高的自描述能力,业务逻辑必须高度解耦。我们在推进 AI First 架构时,面临的最大阻力通常来自老旧系统的技术债。历史遗留的紧耦合代码根本无法被封装成独立的工具供模型调用。

财务场景的拆解

财务场景是典型的低容错度、高确定性要求的领域。将概率模型直接应用于财务核心链路会引发严重的合规风险。可落地的切入点集中在外围的非结构化数据处理和信息流转环节。

发票与报销单据的信息抽取是一个高价值场景。传统 OCR 结合正则匹配在面对版式多变的票据时维护成本极高。引入大模型进行多模态信息抽取,将非结构化的图片或 PDF 转换为结构化的 JSON 数据。抽取后的数据必须经过传统规则引擎的二次校验,例如金额试算平衡验证、税号合规性检查。模型在这里承担的是「粗加工」角色,最终的业务落库动作依然由确定性代码把控。

财务制度问答可以大幅降低沟通成本。基于企业内部报销规范构建 RAG 系统。员工在提单前通过自然语言查询报销标准。这里的 RAG 必须严格限制模型的发散,Prompt 中需强制要求「仅根据检索到的内容回答,未提及的内容直接回复不知道」。为了防止模型编造财务政策,我们会在输出层增加一层文本相似度校验,确保模型的回答与检索到的原文保持高度一致。

财务分析报告初稿生成也是一个可行的方向。将结构化的财务报表数据通过代码转换为文本描述,作为上下文喂给模型,让其生成趋势分析和异常波动提示。模型在这里仅作为「翻译官」和「排版员」,不参与任何数值计算。所有的同比、环比计算必须在传统代码层完成,将计算结果以明确的数值形式提供给模型。让 LLM 去做算术题是工程上的反模式。

数据隐私在财务场景中是不可逾越的红线。公有云 API 无法满足审计要求。我们通常需要采用本地私有化部署的开源模型。7B 到 14B 参数规模的模型经过量化处理后,可以在单张消费级显卡上流畅运行。通过针对财务语料的微调,这些小模型在特定信息抽取任务上的表现可以持平甚至超越千亿参数的通用大模型。私有化部署带来了硬件采购和模型运维的额外成本,需要在项目初期进行严格的 ROI 测算。

以上

Claude Code 的 SKILLS 技能渐进式披露实现原理解析

SKILLS 和 渐进式披露 是 A 家最早提出来的方案,也是 OpenClaw 火了后大家一直讨论的哪个技能好用很核心的强依赖的实现逻辑。

如果把 Claude Code 的 skills 理解成一堆 prompt 文件,后面的很多设计都解释不通。

从其源码实现来看,会发现它在解决的核心问题是:怎么让模型保留足够强的技能召回能力,同时又不把常驻上下文撑爆。

这件事说穿了就是五个字:渐进式披露

大概的逻辑是:

  • 先告诉模型「系统里存在 skills 机制」。
  • 再告诉它「当前有哪些 skill 名称和简短说明」。
  • 等它真的决定调用某个 skill 时,再把正文、权限、hooks、模型覆盖、附加工具权限这些重内容展开。
  • 如果某些 skill 还和路径、目录、文件类型绑定,那就继续往后拖,拖到模型真的碰到对应文件时再激活。

这是一个优雅且干净的工程化设计。它没有发明一套复杂到难以维护的 skill runtime,也没有把所谓智能寄托在黑盒检索器上,而是先把「披露成本」这件事控制住。

我们按工程实现往下拆:

  • skill 在系统里到底被建模成什么
  • 多来源 skill 是怎么统一装配的
  • 渐进式披露具体分了哪几层
  • 条件激活和动态发现是怎么接进文件操作链路的
  • inline 和 fork 两条执行路径分别解决什么问题
  • 这套设计真正适合什么场景,代价又是什么
  • 如果要在自己的 Agent 里复刻,最短落地路径应该怎么走

一、先看 skills 在系统里被建模成什么

Claude Code 里,skill 最终会被统一建模成 Command,而且类型是 prompt

最核心的构造函数是 createSkillCommand:

return {
type'prompt',
  name: skillName,
  description,
  hasUserSpecifiedDescription,
  allowedTools,
  argumentHint,
  argNames: argumentNames.length > 0 ? argumentNames : undefined,
  whenToUse,
  version,
  model,
  disableModelInvocation,
  userInvocable,
  context: executionContext,
  agent,
  effort,
  paths,
  contentLength: markdownContent.length,
  isHidden: !userInvocable,
  progressMessage: 'running',
  userFacingName(): string {
    return displayName || skillName
  },
  source,
  loadedFrom,
  hooks,
  skillRoot: baseDir,
async getPromptForCommand(args, toolUseContext) {
    ...
    return [{ type'text', text: finalContent }]
  },
}

这段代码说明有几个关键点:

  • skill 不是特殊 runtime object,而是 prompt command
  • skill 本体是 getPromptForCommand() 生成的一组文本 block
  • skill 可以带:
    • allowedTools
    • model
    • effort
    • paths
    • hooks
    • context: inline | fork
  • skill 的调用结果,不是「执行一段脚本」,而是把 skill 展开成后续对话消息,或者 fork 成子代理执行

如果我们自己做 Agent,建议参考。skill 不要单独发明一套 DSL runtime,直接把它抽象成「可延迟展开的 prompt 命令」就够了。

二、skills 的来源有哪几类

skills 并不只来自一个目录。getSkills() 会把多个来源统一聚合。[commands.ts] commands.ts#L353-L398

const [skillDirCommands, pluginSkills] = await Promise.all([
  getSkillDirCommands(cwd)...
  getPluginSkills()...
])
const bundledSkills = getBundledSkills()
const builtinPluginSkills = getBuiltinPluginSkillCommands()

然后 loadAllCommands() 再把这些东西和 workflow/plugin/内建命令一起合并。[commands.ts] commands.ts#L445-L469

也就是说,skills 的来源至少有:

  • bundled skills
  • 磁盘上的 /skills/
  • plugin skills
  • builtin plugin skills
  • 兼容旧 /commands/ 目录加载进来的 prompt commands

SkillTool 根本不需要知道 skill 来自哪里。只要最后是 prompt command,就能走统一调用路径。

三、skills 的「渐进式披露」分 5 层

1)第一层:系统提示只声明「技能机制存在」

系统提示里不会把所有 skill 正文直接塞进去。它只给一个能力声明,告诉模型:

  • 用户说 /<skill-name>,其实是在指 skill
  • 可以用 SkillTool 去执行
  • 不要乱猜,只能调用列出来的那些

这段在 [prompts.ts] prompts.ts#L353-L401:

hasSkills
  ? `/<skill-name> (e.g., /commit) is shorthand for users to invoke a user-invocable skill. When executed, the skill gets expanded to a full prompt. Use the ${SKILL_TOOL_NAME} tool to execute them. IMPORTANT: Only use ${SKILL_TOOL_NAME} for skills listed in its user-invocable skills section - do not guess or use built-in CLI commands.`
  : null

这一步只暴露了机制,没有暴露内容

2)第二层:只披露 skill 名称和短描述

真正给模型看的 skill 列表,是通过 getSkillToolCommands() 过滤出来的。[commands.ts] commands.ts#L561-L580

return allCommands.filter(
  cmd =>
    cmd.type === 'prompt' &&
    !cmd.disableModelInvocation &&
    cmd.source !== 'builtin' &&
    (
      cmd.loadedFrom === 'bundled' ||
      cmd.loadedFrom === 'skills' ||
      cmd.loadedFrom === 'commands_DEPRECATED' ||
      cmd.hasUserSpecifiedDescription ||
      cmd.whenToUse
    ),
)

这段有两个要点:

  • 只有 prompt 命令才能进 skill 列表
  • 并不是所有 prompt command 都自动暴露,至少得满足可描述性要求

也就是说,可执行集合对模型披露集合不是完全相同的。
Claude Code 在这里收了一刀,避免模型看到一堆没有描述、无法判断用途的技能。

3)第三层:列表本身还要走预算裁剪

skill 列表不是全量原文塞进 prompt,而是按预算压缩过的。核心逻辑在 [prompt.ts] tools/SkillTool/prompt.ts#L20-L171。

最关键的常量:

export const SKILL_BUDGET_CONTEXT_PERCENT = 0.01
export const DEFAULT_CHAR_BUDGET = 8_000
export const MAX_LISTING_DESC_CHARS = 250

以及格式化逻辑:

return `- ${cmd.name}${getCommandDescription(cmd)}`

和预算裁剪:

if (fullTotal <= budget) {
  return fullEntries.map(e => e.full).join('\n')
}

如果超预算,就会:

  • bundled skills 尽量保留完整描述
  • 其它 skills 截断 description
  • 极端情况下退化成只发 - skill-name

这就是很典型的渐进式披露:先给最小可用索引,不给正文

4)第四层:列表还是增量下发,不是每轮全量重发

技能列表通过 skill_listing attachment 发给模型。发送逻辑在 [attachments.ts] utils/attachments.ts#L2669-L2752。

核心逻辑:

const newSkills = allCommands.filter(cmd => !sent.has(cmd.name))
...
for (const cmd of newSkills) {
  sent.add(cmd.name)
}
...
return [
  {
    type'skill_listing',
    content,
    skillCount: newSkills.length,
    isInitial,
  },
]

这个 sentSkillNames 机制说明:

  • 第一次发的是初始批次
  • 后面只发新增的 skill
  • resume 之后还会 suppress,避免重复污染上下文

然后 messages.ts 会把它包成系统提醒。[messages.ts] utils/messages.ts#L3763-L3772

return wrapMessagesInSystemReminder([
  createUserMessage({
    content: `The following skills are available for use with the Skill tool:\n\n${attachment.content}`,
    isMeta: true,
  }),
])

很多 Agent 会每轮把所有 tools / skills 全量重发,Claude Code 显然在认真控 token。 当然,如果技能不多,也可以直接全量发,不要过早优化。

5)第五层:真正的 skill 内容延迟到调用时才展开

直到调用 SkillTool,skill 的真实正文才会通过 command.getPromptForCommand() 生成。[SkillTool.ts] utils/processUserInput/processSlashCommand.tsx#L869-L920

这里才会发生:

  • $ARGUMENTS 替换
  • ${CLAUDE_SKILL_DIR} 替换
  • ${CLAUDE_SESSION_ID} 替换
  • markdown 内嵌 shell 执行
  • hooks 注册
  • 附加权限 attachment 注入
  • invoked skill 记录

换句话说,skill 的重内容、重权限、重上下文副作用,都是按需加载

四、除了延迟加载,它还做了「条件激活」

这也是渐进式披露的重要一层,而且很多人会漏掉。

1)带 paths frontmatter 的 skill,不会启动即暴露

getSkillDirCommands() 里会把 skill 分成两类:[loadSkillsDir.ts] loadSkillsDir.ts#L771-L803

if (
  skill.type === 'prompt' &&
  skill.paths &&
  skill.paths.length > 0 &&
  !activatedConditionalSkillNames.has(skill.name)
) {
  newConditionalSkills.push(skill)
} else {
  unconditionalSkills.push(skill)
}

然后 conditional skills 被先放进 conditionalSkills map,而不是直接进入模型可见集合。

这意味着:

  • 你定义了某个 skill 只适用于 *.tsx
  • 它不会在项目启动时就干扰所有任务
  • 只有模型真的碰到匹配文件时,这个 skill 才会被激活

2)激活时机挂在文件操作上

FileRead / FileWrite / FileEdit 三个工具里,都有两步副作用:

  • 发现上层目录里的 .claude/skills
  • 激活匹配当前文件路径的 conditional skills

比如 FileReadTool:[FileReadTool.ts] /tools/FileReadTool/FileReadTool.ts#L575-L591

const newSkillDirs = await discoverSkillDirsForPaths([fullFilePath], cwd)
...
addSkillDirectories(newSkillDirs).catch(() => {})
...
activateConditionalSkillsForPaths([fullFilePath], cwd)

对应的激活实现是 [activateConditionalSkillsForPaths] skills/loadSkillsDir.ts#L997-L1058:

const skillIgnore = ignore().add(skill.paths)
...
if (skillIgnore.ignores(relativePath)) {
  dynamicSkills.set(name, skill)
  conditionalSkills.delete(name)
  activatedConditionalSkillNames.add(name)
}

这一步非常像条件规则系统,而不是纯静态注册。
效果就是:技能集合会随着你读写哪些文件而变化

五、动态发现本身也是渐进式披露的一部分

除了 path-conditional activation,Claude Code 还支持目录级动态发现

1)启动时只加载一部分 skill 目录

getSkillDirCommands() 启动时会加载:

  • managed
  • user
  • project dirs
  • additional dirs
  • legacy commands

但它不会把所有嵌套目录里的 .claude/skills 一次性全扫出来。[loadSkillsDir.ts] skills/loadSkillsDir.ts#L638-L804

2)当模型碰到某个文件时,再向上走目录树找嵌套 skill

discoverSkillDirsForPaths() 会从当前文件的父目录开始,一路往上走到 cwd,查找 .claude/skills。[loadSkillsDir.ts] skills/loadSkillsDir.ts#L861-L915

while (currentDir.startsWith(resolvedCwd + pathSep)) {
  const skillDir = join(currentDir, '.claude''skills')
  ...
  await fs.stat(skillDir)
  ...
  newDirs.push(skillDir)
}

而且还做了两个非常实用的约束:

  • 已检查过的目录不会重复 stat
  • gitignored 目录里的 skills 不会静默加载

这个设计让:
技能跟着你进入子目录而出现,不跟整个仓库一起一次性曝光。

六、SkillTool 的调用链,实际上分 inline 和 fork 两条路

这是技能系统和普通 slash command 最大的不同之一。

1)调用前校验

SkillTool.validateInput() 会做:

  • 去掉前导 /
  • 检查 skill 是否存在
  • 检查是否 disableModelInvocation
  • 检查是否为 prompt 类型
    见 [SkillTool.ts] tools/SkillTool/SkillTool.ts#L355-L430

关键逻辑:

const commands = await getAllCommands(context)
const foundCommand = findCommand(normalizedCommandName, commands)
...
if (foundCommand.type !== 'prompt') {
  return {
    result: false,
    message: `Skill ${normalizedCommandName} is not a prompt-based skill`,
  }
}

2)权限检查

SkillTool.checkPermissions() 很细,除了 allow / deny 规则,还会对「只有安全属性的 skill」自动放行。[SkillTool.ts] /tools/SkillTool/SkillTool.ts#L433-L579

这个设计的意义是:

  • 简单 declarative skill 不必每次都弹权限
  • 带额外风险属性的 skill 要 ask user

3)inline skill:展开成后续对话消息

默认分支会走 processPromptSlashCommand()。[SkillTool.ts] tools/SkillTool/SkillTool.ts#L635-L644

getMessagesForPromptSlashCommand() 干的事情很丰富:[processSlashCommand.tsx] utils/processUserInput/processSlashCommand.tsx#L827-L920

  • command.getPromptForCommand(args, context) 得到真正 skill 正文
  • 注册 hooks
  • addInvokedSkill() 记录 skill 内容,供 compact 时恢复
  • 从 skill 文本里再抽 attachment
  • 增加 command_permissions attachment
  • 生成一批 messages

返回结构里最关键的是:

return {
  messages,
  shouldQuery: true,
  allowedTools: additionalAllowedTools,
  model: command.model,
  effort: command.effort,
  command
}

也就是说,inline skill 的本质是:
把 skill 变成一段新的上下文和权限修饰,然后让主对话继续跑。

4)fork skill:交给子代理跑,再把结果归还

如果 skill frontmatter 里声明 context === 'fork',就走 executeForkedSkill()。[SkillTool.ts] tools/SkillTool/SkillTool.ts#L622-L633

它会:

  • 构造子代理上下文
  • runAgent()
  • 收集 agent messages
  • 抽取结果文本
  • 最终返回 { status: 'forked', agentId, result }
    见 [executeForkedSkill] /tools/SkillTool/SkillTool.ts#L122-L290

这一步说明 Claude Code 已经把 skill 分成两类:

  • 知识/流程模板型 skill:inline 展开
  • 工作委派型 skill:fork 子代理执行

这个值得学一下。不是所有 skill 都应该展开在主上下文里。

七、结果返回逻辑

为什么它也算渐进式披露的一部分?

1)inline skill 的 tool_result

很轻

mapToolResultToToolResultBlockParam() 对 inline skill 的返回只是:

content: `Launching skill: ${result.commandName}`

见 [SkillTool.ts] tools/SkillTool/SkillTool.ts#L857-L862

也就是说,tool_result 本身不承载 skill 的全部结果。
真正有价值的内容在 newMessages 里,已经被送回主会话继续推理。

2)fork skill 的 tool_result

直接带最终结果

fork skill 返回的是:

content: `Skill "${result.commandName}" completed (forked execution).\n\nResult:\n${result.result}`

见 [SkillTool.ts] tools/SkillTool/SkillTool.ts#L848-L855

这是因为 fork skill 已经在独立上下文里把工作做完了,主线程要拿的是总结结果。

所以在 Claude Code 里,skill 结果返回不是单一模式,而是:

  • inline:返回「已加载 skill」,真正内容进主对话
  • fork:返回「子代理执行结果」

这也是一种披露控制。
不同执行语义,对结果暴露方式也不同。

八、如何简要实现

一个新 Agent,如何简要实现 skills 的发现、召回、调用、结果返回?

一个够用、够短、能落地的最小设计,不追求和 Claude Code 一模一样,但核心思路一致。

1)第一步:统一 skill 数据结构

最小结构建议这样:

type Skill = {
  name: string
  description: string
  whenToUse?: string
  contentLoader: (args: string, ctx: AgentContext) => Promise<string>
  allowedTools?: string[]
  model?: string
  effort?: 'low' | 'medium' | 'high'
  context?: 'inline' | 'fork'
  paths?: string[]
}
  • contentLoader 允许延迟展开
  • context 决定 inline/fork
  • paths 支持条件激活
  • allowedTools/model/effort 支持 skill 级上下文修饰

这和 Claude Code 的 createSkillCommand() 思路是一致的。[loadSkillsDir.ts] skills/loadSkillsDir.ts#L270-L401

2)第二步:启动时只加载「索引」,不要加载正文

最简做法:

  • 扫描 skills 目录
  • 解析 frontmatter
  • 只把 name / description / whenToUse / paths / context 放进 registry
  • skill 正文不要此时进 prompt

示意:

async function loadSkillIndex(skillDirs: string[]): Promise<Skill[]> {
const skills: Skill[] = []
for (const dir of skillDirs) {
    for (const skillFile of await listSkillFiles(dir)) {
      const raw = await readFile(skillFile, 'utf8')
      const { frontmatter, content } = parseFrontmatter(raw)
      skills.push({
        name: basename(dirname(skillFile)),
        description: String(frontmatter.description ?? ''),
        whenToUse: frontmatter.when_to_use ? String(frontmatter.when_to_use) : undefined,
        paths: Array.isArray(frontmatter.paths) ? frontmatter.paths : undefined,
        context: frontmatter.context === 'fork' ? 'fork' : 'inline',
        contentLoader: async () => content,
      })
    }
  }
return skills
}

这个阶段要学 Claude Code 的不是目录细节,而是索引和正文分离

3)第三步:做一个「未发送 skill 集合」

这是渐进式披露的核心。

维护一个 session 级状态:

type SkillDisclosureState = {
  sentSkillNames: Set<string>
}

每轮只发送新的:

function getNewSkillListings(skills: Skill[], sent: Set<string>): Skill[] {
  const fresh = skills.filter(s => !sent.has(s.name))
  for (const s of fresh) sent.add(s.name)
  return fresh
}

然后把它格式化成短列表,而不是全文:

function formatSkillListing(skills: Skill[]): string {
  return skills.map(s => `- ${s.name}${s.description}`).join('\n')
}

这对应 Claude Code 的 sentSkillNames + skill_listing attachment 方案。

4)第四步:把文件操作接成动态发现触发器

如果你也想要「技能跟着目录出现」,最小版本就是:

  • 用户或模型读/写/改文件时
  • 从文件父目录往上走到 cwd
  • 看有没有 .agent/skills 或 .claude/skills
  • 找到新目录就加载 skill index

示意:

async function discoverSkillDirsForFile(filePath: string, cwd: string): Promise<string[]> {
const dirs: string[] = []
let current = dirname(filePath)
while (current.startsWith(cwd + sep)) {
    const candidate = join(current, '.agent''skills')
    if (await exists(candidate)) dirs.push(candidate)
    const parent = dirname(current)
    if (parent === current) break
    current = parent
  }
return dirs
}

Claude Code 的现成参考是 [discoverSkillDirsForPaths] skills/loadSkillsDir.ts#L861-L915。

5)第五步:做条件激活,而不是启动时全暴露

如果 skill 定义里有 paths,就不要一开始暴露。
等碰到匹配文件时再激活:

function activatePathScopedSkills(
  pending: Skill[],
  touchedFiles: string[],
): { active: Skill[]; remaining: Skill[] } {
const active: Skill[] = []
const remaining: Skill[] = []
for (const skill of pending) {
    if (!skill.paths || skill.paths.length === 0) {
      active.push(skill)
      continue
    }
    const matched = touchedFiles.some(file => matchAny(file, skill.paths!))
    if (matched) active.push(skill)
    else remaining.push(skill)
  }
return { active, remaining }
}

这就是 Claude Code conditionalSkills -> activateConditionalSkillsForPaths() 的最小复刻。


6)第六步:调用 skill 时才真正加载正文

不要提前把 skill 正文塞到 prompt。
调用时再做:

async function invokeSkill(
  skill: Skill,
  args: string,
  ctx: AgentContext,
): Promise<SkillInvocationResult> {
const prompt = await skill.contentLoader(args, ctx)

if (skill.context === 'fork') {
    const result = await runSubAgent({
      prompt,
      allowedTools: skill.allowedTools,
      model: skill.model,
      effort: skill.effort,
    })
    return { mode: 'fork', result }
  }

return {
    mode: 'inline',
    newMessages: [
      { role: 'user', content: `[SKILL:${skill.name}]` },
      { role: 'user', content: prompt, meta: true },
    ],
    allowedTools: skill.allowedTools,
    model: skill.model,
    effort: skill.effort,
  }
}

这就是 Claude Code SkillTool.call() 的最小骨架。[SkillTool.ts] tools/SkillTool/SkillTool.ts#L581-L863

7)第七步:结果返回必须分 inline 和 fork

直接照 Claude Code 的语义分两种:

inline

  • 返回一个轻 tool_result:Launching skill: xxx
  • 真正内容通过 newMessages 回到主对话继续推理

fork

  • 返回最终结果摘要
  • 子代理对话不污染主上下文

示意:

type SkillInvocationResult =
  | {
      mode: 'inline'
      newMessages: Message[]
      allowedTools?: string[]
      model?: string
      effort?: string
    }
  | {
      mode: 'fork'
      result: string
    }

这一步是很多新 Agent 最容易偷懒的地方。
要么所有 skill 都 inline,主上下文爆炸;要么所有 skill 都 fork,失去细粒度引导。

九、小结

「skills 的渐进式披露」其实就是 Claude Code 在控制 prompt 成本和能力密度时最典型的设计之一。它真正解决的问题不是「怎么找到一个 skill」,而是「怎么在不把上下文撑爆的前提下,让模型知道自己有技能可用」。

它背后的思路:

  • 先给索引
  • 再给局部集合
  • 再给真实正文
  • 最后才给执行结果

这是一个很像搜索引擎的设计:摘要、点击、展开、消费,而不是把整本书扔给你。

以上。

 

Claude Code 的下一个 AI 范式:KAIROS

在 Claude Code 的代码中,如果只算 KAIROS 出现的次数,其出现了 154 次;如果算上以其为前缀的变量啥的,其出现了 365 次。

KAIROS 是什么?

简单来说,KAIROS 是 Claude Code 未来的 AI 形态,一个在恰当时机出现的,一直在线的协同工作伙伴。

KAIROS (καιρός) 源自古希腊语,意为「正确的、关键的或合宜的时刻」,代表定性的、超越时序的「时机」或「关键瞬间」。

KAIROS 这件事,重点从来不在于它多了几个工具开关,也不在于文档里写了多少「常驻助手」「主动工作」这种产品话术。它真正改变的,是 Claude Code 的运行范式:从「终端里的同步问答器」,切到「长期在线、异步协作、跨渠道接入、能自己维持工作节奏的常驻代理」。

这个变化很大。大到我不太愿意把它叫成一次功能升级。它更像一次产品类别切换。

如果这个方向跑通,Claude Code 的竞争对象会变。它不再只是和一批 coding assistant CLI 去比「补全快不快、命令懂不懂、上下文长不长」。它会开始进入另一条赛道:谁更像一个持续在线的工程协作者,谁能承接跨时间、跨终端、跨系统的工作责任。

问题也在这里。KAIROS 现在的仓库状态,远没有到「产品封版」的程度。外围能力已经长出不少,主闭环还没彻底打穿。Bridge、Brief、频道消息、每日记忆日志、后台任务基础设施,这些都不是 PPT。assistant 主入口、gate、proactive 状态、session discovery 这些地方,又明显还是 stub。方向很清楚,骨架也搭起来了,真正决定产品能不能稳定落地的那几条主链路,还差最后一截硬骨头。

这篇文章我们不按功能清单来复述一遍。那样太浅,也没工程价值。回答三个关键的问题:

  1. KAIROS 在 Claude Code 里到底重新定义了什么
  2. 它已经落地了哪些关键能力,哪些地方还没闭环
  3. 为什么它必须改写记忆系统、交互渠道和执行模型

KAIROS 改写的不是功能,是运行模型

普通 CLI 的交互模型很简单。

用户打开终端。输入一条指令。模型分析上下文。调用工具。给出回答。进程结束,或者这一轮逻辑结束。下一次再来,虽然可能还能靠项目文件、历史记录、memory 文件接上一部分语境,但本质上还是新一轮同步请求-响应。

这个模式有一个天然上限:AI 只在用户看着终端的时候存在。用户不在,系统就不工作。外部事件来了,也接不住。长任务只能靠用户盯着。跨设备继续工作这件事,基本也无从谈起。

KAIROS 想改掉的,就是这个上限。

它想要的模型是另一种结构:

  • 会话可以长期存在
  • 进程重启后还能接回原来的会话
  • 外部系统可以把消息推到这个会话里
  • 用户没有新输入时,Agent 也能继续推进任务
  • 工作状态通过记忆、日志和摘要维持
  • 输出形态适配异步消费,而不是只适配终端前的一次性阅读

这不是「更主动一点」。这是一整套运行时假设变了。

我一直觉得,很多人看这类能力时容易掉进一个误区:看见 SleepTool、push notification、channels,就以为这只是「给 CLI 加点自动化」。这个理解有点太保守。真正的变化是,系统开始假设自己是一个持续值班的实体,而不是一个按回车键才苏醒的函数调用。

一旦假设变了,后面的东西都会跟着变。会话管理会变。记忆策略会变。输出格式会变。安全边界会变。成本模型会变。产品定位也会变。

从代码现状看,KAIROS 已经是一组能力家族

从文档和源码状态看,KAIROS 不是单点 feature flag,它更像一个能力总开关,把若干子系统串成一个共同叙事。

已有的子功能包括:

  • KAIROS_BRIEF
  • KAIROS_CHANNELS
  • KAIROS_PUSH_NOTIFICATION
  • KAIROS_GITHUB_WEBHOOKS
  • KAIROS_DREAM

工具注册层能看到对应工具:

  • SleepTool
  • SendUserFileTool
  • PushNotificationTool
  • SubscribePRTool

从这些就可能看到背后对应的产品动作:

  • 控制执行节奏和保活
  • 主动把结果回传给用户
  • 在终端外做异步通知
  • 订阅外部事件,反向驱动内部执行

这里最值得注意的是其产品动作的形态已经变了。

传统 CLI 工具的动作集合通常是:

  • 读文件
  • 写文件
  • 执行命令
  • 输出结果

KAIROS 的动作集合变成:

  • 等待
  • 监听
  • 回传
  • 唤醒
  • 跨渠道接入
  • 持续维持上下文

这说明它的定位已经不再是单纯的「本地操作器」。它正往「工作流中枢」走。

Bridge 是 KAIROS 最关键的基础设施之一

KAIROS 能不能成立,第一件事不是主动性,而是连续性

如果会话不能连续存在,所谓常驻助手就是假的。它最多是一个本地守护进程。用户侧体验还是断裂的。今天在一个终端开的事情,明天换个终端、换个设备、换个入口,就接不上了。那这个产品心智根本立不起来。

Bridge 正是在补这个问题。

从现有设计看,Bridge 的数据流:远端入口收到用户消息,通过 bridge 拉取工作,创建或恢复 REPL,会话继续执行,再把结果回传。这个思路解决的是「用户感知到的是不是同一个持续存在的助手」。

代码里关键点在 useReplBridge。assistant 模式下会启用 perpetual bridge session,目的是让远端看到的是同一条持续会话,而不是每次 CLI 启动都开一条新的 session。

没有 perpetual session,用户面对的是很多段相似但断裂的对话。每次恢复都像重新认识一次项目。上下文可能靠 memory 拼回来一点,但主观体验一定是断的。

有了 perpetual session,用户会开始把这个东西当成「同一个一直在线的协作者」。这就是范式变化真正落地的第一步。

但真实闭环卡住的地方,不在 Bridge 本身

Bridge 骨架基本有了,assistant 产品主链路还没闭环。

很多团队做 Agent 系统时,最容易陷入一种错觉:底层传输能通,远程会话能恢复,消息能送达,就以为产品主路径已经打通。其实不是。通道打通,只代表系统能传递状态。离「一个稳定可用的常驻助手产品」还差几个关键层:

  • 身份判定
  • gate 放行
  • 会话发现
  • assistant 专属上下文初始化
  • assistant 专属系统提示
  • 持续工作状态机
  • 长期记忆蒸馏

当前源码里,这些都还没有。

assistant 主入口还是 stub

assistant 主模块里,isAssistantMode() 返回 false,初始化函数是空的,assistant 专属 prompt addendum 也是空的。

这意味着什么?

意味着一大堆外围逻辑虽然预留了 assistant 分支,但真正运行时根本进不去。Bridge 想按 assistant 模式走 perpetual session,要靠 isAssistantMode()。主程序想按 assistant 模式切换运行逻辑,也要靠它。远端 worker 类型的区分,还是要靠它。

入口判定没实现,后面这条链就全断了。

KAIROS gate 还是 stub

isKairosEnabled() 直接返回 false,这表示:产品级放行逻辑还没接上。

这不是个小洞。因为常驻助手和普通 CLI 完全不是一个风险等级的东西。它能后台执行、接外部消息、长期持有上下文、主动做事。没有真实 gate,这种能力根本不适合大面积开。

所以从工程视角看,gate 现在是缺实现。从产品视角看,这代表「产品入口控制逻辑还停留在骨架层」。

session discovery 还是 stub

如果用户执行 assistant viewer 路径,系统理论上应该能发现已有常驻会话,然后接回去。现在 discovery 返回空数组,这条体验链路就断了。

这直接伤到产品最核心的承诺之一:会话连续性。

你都号称是常驻助手了,结果用户回来时找不到自己之前那条会话,这个心智会瞬间塌掉。

proactive 状态模块还是 stub

KAIROS 的 prompt 层已经开始定义 autonomous work 的行为协议了,比如:

  • 没有事做时必须 sleep
  • 用户不在终端前时偏向自主推进
  • 用户正在看终端时偏向协作和简洁输出

行为协议有了,状态机没落地。

KAIROS 最大的区别,不是工具多,而是 prompt 协议变了

对于 Agent 系统,prompt 在很多时候就是运行协议的一部分。

普通模式下,模型更像一个执行器。收到请求,完成任务,给出答复。

KAIROS 模式下,prompt 在定义另一种工作方式:

  • 什么时候该自己推进
  • 什么时候该停下来等待
  • 什么时候该用 Brief 压缩输出
  • 什么时候该把结果异步推给用户
  • 用户是否在终端前,会影响表达风格和协作策略
  • 没有明确工作时不能空转,必须 sleep

这本质上是在给模型灌输一套「值班协作者协议」。

因为常驻助手和一次性问答器的差别,很大一部分在于它是否具备稳定的工作节奏。节奏不稳,再强的工具集也会把系统拖进两个极端:

  • 一直唤醒,疯狂消耗 token 和 API 调用
  • 一直沉睡,错过事件和推进时机

所以 SleepTool 这种东西,表面看是个小工具,本质上是在为 Agent 增加时间维度。普通 CLI 处理的是空间里的资源:文件、命令、输出。KAIROS 开始处理时间里的资源:等待、唤醒、周期、空闲、值班。

这一步一旦做出来,产品形态就变了。

KAIROS 为什么必须改写记忆系统

这里必须要聊一下。

在长任务中,我们不能拿短会话的记忆模型去硬撑长时间在线系统。写放大、污染、冲突、检索噪音、摘要失真,很快全出来。

KAIROS 在这件事上切到了 daily log 模式。

普通模式下,长期记忆更接近「主题文件 + 索引」:

  • 新信息被整理成相对成型的 topic files
  • MEMORY.md 维护索引
  • 模型下次需要时按主题读回

这个模式适合短周期会话。信息密度高,整理成本还能接受。

KAIROS 场景不一样。它面对的是:

  • 长时间持续执行
  • 高频事件流输入
  • 用户不一定实时盯着
  • 外部渠道消息可能随时插入
  • 同一天内工作状态会不断变化
  • 大量信息是过程态,不适合立刻主题化

如果还按普通模式那种「一有信息就整理成 topic file」去写,工程上会出三个明显问题。

第一,写放大会很严重

频繁改 topic files,会导致目录不断抖动。MEMORY.md 也会被频繁重写。对于常驻系统,这种写入模式很不稳。量一上来就开始恶心人。

第二,过程信息会过早结构化

很多工作过程在当下并不适合写成结论。比如一条外部消息、一次等待中的验证、一个还没确认的假设、某项任务中间状态。这些东西如果过早塞进长期记忆,很容易污染后续上下文。

第三,恢复和追溯会变差

当你把所有信息即时揉进主题文件里,原始事件流会逐渐丢失。系统后面做蒸馏、回溯、纠错时,材料反而不够。

daily log 方案就是为了解这些问题。

它的核心思想很简单:

  • 白天先 append-only 记录到当日日志
  • 不急着重组和提炼
  • 后续再把成熟信息蒸馏成长期 memory

这是典型的事件流优先设计。先保留工作轨迹,再做结构化提炼。对常驻助手来说,这个方向很稳。

普通模式里的 memory 是模型的记忆补丁,KAIROS 里的 memory 是产品连续性的基础设施。

这两者不是一个级别的东西。

transcript 被纳入记忆蒸馏

KAIROS 还想做一件更重要的事:把 session transcript 也纳入记忆蒸馏输入。

这意味着长期记忆的来源,不再只靠模型「当前轮总结出的信息」,而是开始吸收完整工作轨迹。

你可以把这理解成两种 memory 策略的差异:

普通模式

长期记忆主要存「结论」

KAIROS 模式

长期记忆想从「事件流」中提取结论

因为一个持续在线的协作者,真正有价值的上下文往往不只在最后结果里,还在过程里:

  • 谁在什么时候发来过什么消息
  • 某项任务等待了多久
  • 哪个验证步骤失败过
  • 某个方向为什么被放弃
  • 同一项目在不同日期里怎么演化的

如果记忆系统拿不到这些过程信息,系统就只能越活越像一个失忆的执行器:只记结论,不记来路。

当前仓库里这条链还没补齐,session transcript 相关实现还是 stub。当把普通 auto-dream 关闭,改走 KAIROS 专属 dream 路径,那就必须有足够材料来做蒸馏。daily logs 和 transcript 就是这个材料池。

没有材料池,dream 只是概念。
没有蒸馏,daily log 只是堆积。
没有长期记忆收敛,常驻助手就只剩「一直在线」,没有「越用越像同一个协作者」。

Brief 不是 UI 花活

它是异步协作场景里的输出压缩层

我很喜欢 KAIROS 里对 Brief 的定位,因为这个点很多产品会忽略。

一旦系统进入长期运行、跨终端、跨渠道、还带移动端通知的场景,传统那种长篇回复会迅速失效。不是模型写不出来,而是用户根本没法消费。

你想象一下:

  • 一个后台任务跑了两小时
  • 外部 webhook 触发了一轮检查
  • Slack 里推来一条状态更新
  • 用户此时在手机上看通知

这时候如果系统还按终端里的详细答复风格,甩一大段解释文字出去,体验会很差。信息密度低,确认成本高,真正关键的状态反而埋住了。

Brief 的价值就在这里。它是异步工作场景的输出压缩层。

它解决的不是「怎么更优雅地显示」,而是「在不同消费界面里,用最低认知成本传递足够状态」。这是一个工程问题,不是文案问题。

所以我会把 Brief 看成 KAIROS 的必要配套,而不是锦上添花。没有它,常驻助手很容易被自己的输出拖死。

channels 让 Claude Code 开始脱离终端边界

KAIROS 另一条很重要的线,是频道消息系统,也是一个很让人期待的逻辑,虽然当前有一些方法已经可以实现。

当前设计已经允许外部消息通过 channel notification 之类的机制进入会话,被包装成结构化消息再投递给模型处理。这意味着:

  • Claude Code 不再只属于一个本地终端
  • 用户可以在终端外部和同一条工作流继续互动
  • AI 可以成为跨渠道的工作代理

这个变化一旦做成,产品就会从 Developer Tool 往 Agent Platform 滑过去。

再直白一点。

终端工具的边界,是「你必须来到我的界面里,我才能帮你做事」。
工作流代理的边界,是「我能在你的工作流里持续存在,你在哪个入口出现,我都能接上」。

这是两种完全不同的产品位置。

这样,channels 就很重要了,但不该排在最前面的优先级。因为它解决的是入口扩展问题,不是主闭环问题。一个内部还没站稳的系统,入口接得越多,事故面越大。没有 assistant 激活链、记忆蒸馏链和 proactive 状态机托底,channels 只会让问题更快暴露。

后台执行是一等能力,不是附属能力

KAIROS 把后台执行推成一等能力。

这是常驻助手能否成立的另一条底线。

如果 Agent 像同事一样持续工作,它就不能被单次命令执行周期绑死。用户离开终端,任务还得继续跑。长任务不能把主线程挂住。等待回调、监听事件、轮询状态这些行为,不能都靠用户盯着终端来维持。

很多 coding assistant 在 demo 阶段看起来很聪明,一到真实项目就暴露上限:用户一旦离开终端,整个系统的价值密度就迅速下降。长任务没人接。回调没人等。状态没人维持。所谓 Agent,最后还是个问答器。

KAIROS 显然想突破这个上限。

后台执行一旦变成一等能力,系统复杂度会明显上升。你要处理:

  • 阻塞任务后台化
  • 任务状态跟踪
  • 唤醒与恢复
  • 错误回传
  • 与记忆系统的状态对齐
  • 与通知节奏的协调

这些东西没有一项是白送的。做不好,后台任务会变成后台事故。

KAIROS 的产品价值,核心在五个地方

如果从产品结果看,我会把它的价值拆成五个方面。

一,留存会显著提升

一次性问答工具的留存天然一般。因为每轮交互都相对独立,用户用完就走。上下文积累浅,切换成本低。

常驻会话、跨重启续接、长期记忆、异步回传这些东西组合起来,用户会开始把 Claude Code 当成「当前项目的长期协作体」。一旦心智变成这样,迁移成本就会明显上升。

二,任务完成度会提升

很多高价值任务不是一轮 prompt 能做完的。它们需要等待、重试、监听、回调、验证、持续推进。普通模式下,这类任务经常会在用户离开终端时中断。

KAIROS 提供的后台执行、sleep 唤醒、外部事件驱动,正好在补这个缺口。产品会从「答题器」往「任务执行器」走。

三,渠道覆盖会扩大

有了 channels、push、bridge、webhook,Claude Code 的触点会明显增加。对产品运营和组织传播来说,这个价值非常直接。一个只能在终端里使用的工具,天然局限在一小撮高频命令行用户。一个能进入 Slack、移动通知、远程 viewer 的系统,扩散面会大得多。

四,粘性会增强

session continuity、daily logs、structured brief、跨渠道接续,这几样东西叠在一起,会形成很强的黏着效应。用户会越来越依赖它手里的上下文。上下文越深,替换成本越高。

五,产品想象空间会被抬高

做到这里,Claude Code 的竞争对象就不再只是其它 coding assistant。它会开始接近「开发团队的操作层代理」:监听、执行、回报、沉淀记忆、跨渠道协作。

KAIROS 的代价非常重

讲到这里如果只谈价值,那就是宣传稿了。工程里没有这么轻松的事。

KAIROS 的代价主要有四类。

第一,系统复杂度暴涨

从一次性 CLI 进入常驻模式后,系统要处理的东西会指数级增加:

  • 长生命周期会话
  • bridge 重连
  • 会话恢复
  • 远端消息幂等
  • 外部事件接入
  • 后台任务状态
  • 唤醒节奏
  • 记忆蒸馏
  • 多渠道权限边界
  • 通知频率控制

这些全都会增加测试成本、排障成本、回归成本。

传统 CLI 很多问题是可复现、可局部调试的。常驻 Agent 的问题常常是跨时间、跨系统、跨状态累积的。定位难度完全不是一个量级。

第二,成本模型会变差

tick + sleep 这套机制,本质上是在用更多调用换取持续在线行为。架构会更顺,产品体验会更强,API 成本也会上去。

如果没有很严格的唤醒控制、任务优先级控制和输出压缩策略,系统会非常烧钱。尤其当常驻会话一多,哪怕每个会话只是周期性地「看一眼有没有事」,成本都可能迅速放大。

很多团队做 Agent 产品,死得最早的往往不是能力不够,是成本失控。KAIROS 这条路如果真要产品化,成本治理必须和功能迭代同步推进,不能等做大了再补。

第三,安全与信任门槛会更高

一个普通 CLI 助手,最多是「用户下命令,它帮你执行」。
一个 KAIROS 式常驻助手,是「它持续持有上下文,接外部消息,可能在后台自主执行」。

这两个系统的风险等级完全不同。

assistant 模式下先检查 trusted directory,再检查 KAIROS gate,这个设计信号已经很明确:作者知道风险在上升。问题是现在 gate 还是 stub,主链路还没完全接上,说明这块还在建设中。

产品能不能卖进团队、卖进企业,很多时候就看这里。因为企业不会只问「它能做什么」,他们会问得更直接:

  • 它什么时候能自己执行
  • 谁能给它发消息
  • 它能看到哪些目录
  • 它会把什么记下来
  • 它错了怎么停
  • 它怎么审计

这些都是 KAIROS 必须正面回答的问题。

第四,产品承诺和实现闭环还没完全对齐

这是当前最现实的问题。

外围能力铺得已经不少,主入口和主状态层仍然有 stub。这个状态很典型:战略方向先行,支撑设施先铺,真正的产品主通路还在补。

这种状态有机会,也有风险。

机会在于,一旦主入口补齐,成长速度可能很快,因为外围都在等它。
风险在于,如果主闭环补得太慢,外围越多,系统越像「展台很大,地基不稳」。

为什么我认为 KAIROS 是整个项目里最重要的方向

因为它决定的不是某个 feature,而是产品类别。

没有 KAIROS,Claude Code 依然可以是一个很强的 coding assistant CLI。
有了 KAIROS,而且真做成了,它会变成一个持续在线、能跨渠道、能长期记忆、能异步执行的工程助手。

这两者在商业形态上不是一个东西。
在用户心智上不是一个东西。
在组织采购逻辑上也不是一个东西。

对个人开发者,这意味着「我下班以后,它还能继续跑」。
对团队协作,这意味着「我们多了一个持续在线的 AI teammate」。
对企业管理者,这意味着「AI 被接入的是工作流,不是单次问答」。

这三层价值如果连起来,产品天花板会明显抬高。

我甚至会说,KAIROS 成不成,决定了 Claude Code 最终是一个强工具,还是一个强平台。

当前仓库里,KAIROS 的真实成熟度如何?

大概是四点:

  • 产品意图非常清楚: 因为从文档、prompt、memory 策略、bridge 设计、channels、brief,到后台任务工具,整个方向是一致的。不是东一块西一块拼起来的。它们都在服务同一个产品叙事:把 Claude Code 推成常驻助手。
  • 框架布线已经做了很多: 工具层、提示词层、bridge 层、memory prompt 分叉、channel notification、部分 viewer 路径,这些都已经不是空想。它们证明团队不是在写概念文档,而是在提前铺路。
  • 关键外围能力有真实实现:Bridge perpetual session、频道消息接入、Brief 规则、daily-log memory prompt,这些都具备产品骨架。哪怕主入口还没封口,外围支撑已经能看出最终形态。
  • 主入口和核心状态闭环仍有明显缺口:assistant 主模块、gate、session discovery、proactive 状态、session transcript 等地方的 stub,说明核心闭环还没完全长实。这个阶段我会把它定义为:接近产品化的战略子系统,而不是一个完整封版的功能包。

KAIROS 的本质,是把 Claude Code 从「提效工具」推向「责任承接者」

文章写到这里,其实主已经很清楚了。

KAIROS 真正改变的,是 Claude Code 的「存在方式」。

普通 Claude Code 的存在方式是:用户打开终端,它出现;终端关闭,这轮交互的主价值结束。
KAIROS 的存在方式是:用户不在场时,它仍然能持有状态、接收事件、维持节奏、积累记忆、回传结果。

它在把 Claude Code 从前台交互工具,推向后台协作代理。

一旦这个方向跑通,产品的护城河也会变。将来真正难被替代的,不只是模型回答质量,而会落在这些更重的东西上:

  • 上下文沉淀深度
  • 渠道接入深度
  • 工作流嵌入程度
  • 组织内协同能力
  • 长期责任承接能力

这才是 KAIROS 值得持续投入的原因。

小结

KAIROS 已经完成了产品方向的自洽,完成了相当一部分外围基础设施铺设,正在卡在主入口、记忆蒸馏和自主循环这三条上。只要这三条主链补齐,它很快就会从「战略子系统」变成「真正定义 Claude Code 下一阶段的核心产品」。

这件事一旦做成,Claude Code 的故事就不再是「一个更强的 coding assistant CLI」。

它会变成另一个东西。

  • 一个持续在线的工程协作者。
  • 一个能接进团队工作流的后台代理。
  • 一个真正开始承接持续工作责任的 AI 系统。

这才是 KAIROS 的星辰大海。

以上。