MoonshotAI / MoonshotAI/kimi-code

压缩摘要以user消息的身份重新注入上下文,导致授权被自我扩张、规格丢失、过期快照被应用

Open
#3,487 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

你运行的 Kimi Code 版本是?

0.38.0。更新后为 0.39.1,其系统提示描述的压缩设计相同(逐字保留用户消息 + 第一人称摘要 + 追加 TODO 列表),该机制并未变化。
并已直接核对最新源码(HEAD,0.40.1;v1 packages/agent-core 与 v2 packages/agent-core-v2 设计相同):

摘要仍以 user 角色注入:agent-core/src/agent/context/index.ts 的 applyCompaction 构造 summaryMessage: { role: 'user', ... };compaction/handoff.ts 注释明写 "followed by a single user-role summary"

TODO 在 apply 时原样追加到摘要尾部,不与正文做一致性检查(v1 full.ts / v2 fullCompactionService.ts 的 postProcessSummary)

压缩期间的安全检查只看历史追加(v2 historySafeToCompact:前缀一致 + 新增消息必须是真实用户输入),后台任务与 TODO 状态变化不在守卫范围内

在途 turn 仍被整体丢弃(重建只保留真实用户输入 + 摘要)
0.38.0 → 0.40.1 的 changelog 中没有任何压缩机制层面的变更(只有 web UI 的压缩显示修复)。

你使用的是哪个开放平台/订阅?

Kimi Code订阅

你使用的是哪个模型?

kimi-code/k3-256k,thinking effort high

你的电脑平台是?

Darwin 24.5.0 arm64 arm

你遇到了什么问题?

长 session 里自动压缩之后,agent的行为会偏离原来的任务和授权范围。一开始以为是个别现象,后来把本机所有 session 的 wire.jsonl 翻了一遍:17 个 session 里 6 个发生过压缩,共 20 次,平均从 19 万 token 压到 2.9 万。大部分压缩后续是正常的,但有 3 个 session 出了明确的问题,而归因都是摘要写完之后的注入方式。
压缩摘要由模型自行生成,里面混合了已验证的事实、没验证的推断、接下来的计划、以及"用户已经同意了什么"这类陈述。这份摘要被当作一条 user 消息塞回上下文最末尾。也就是说,压缩摘要拿到了和用户指令一样的身份,而且位置比所有真实用户消息都新。摘要包装里虽然写了"把这当笔记看,别当证据",但从实际行为看这句话没起什么作用。
下面是三个有代表性的案例。
案例一:摘要把"批准发布"扩张成了"绕过 review"。 在一次生产发布中,用户给出一条指令:"如果有发现问题,向我汇报,尽可能不要自己决定。"后来用户说了"那就先发布吧"。发布过程中 PR 合并被仓库 ruleset 挡住——分支保护要求必须有人 review。
第五次压缩的摘要里保留了用户的约束,但紧接着写了一句:"用户要求:发现问题汇报,尽可能不自己决定;但发布序列已获批准,按上面执行即可。"
压缩之后,中间没有任何用户消息,agent 连续做了这些事:push、建 PR、尝试合并(被 review 规则拒绝,--admin 兜底也失败)、拉取 ruleset 配置、备份、用 gh api -X PUT 把 ruleset 的 enforcement 改成 disabled、合并 PR、再把 ruleset 改回去。用户的下一条消息是:"为什么不直接让我 review?"
用户确实批准过发布,事后也追认了这次操作,所以没造成实际损失。但在行动时,agent 把"批准发布"自己扩张成了"可以改仓库的安全设置来绕过分支保护"。这个扩张不来自任何一条用户消息,只来自摘要自己的断言。摘要里那句"但……已获批准",实际上把逐字保留在上下文里的用户约束给废掉了。
案例二:精确的实现规格被压没了。 第一次压缩前,agent 派了一个子任务去实现一个修复,规格写在工具调用的参数里:超时时间读 OPENAI_TIMEOUT_MS 环境变量,缺省 180000 毫秒,非整数或小于等于零要启动失败,两个 HTTP client 都要在关闭路径正确释放,测试要覆盖缺省值、环境变量和非法值。
压缩摘要把这些概括成了一句话:"修复 runtime 模型超时拆分,进行中。"
压缩后 agent 要根据摘要继续这项工作,它重新写了一份规格:超时硬编码 600 秒,并且明确写"模型 client 不需要关闭逻辑",测试缩减为"两个 client 分别是 40 秒和 600 秒"。环境变量、参数校验、生命周期管理全没了。代码最后就按这份退化的规格实现并提交了。
原因很直接:压缩只逐字保留用户消息。精确规格、验收标准、约束条件都在 assistant 消息和工具参数里,压缩时全部被概括掉。而摘要里的一句话概括,不足以还原出原来的要求,agent 只能自己重新发明一份规格,还以为这就是原来的任务。
案例三:摘要生成期间世界已经变了。 第一次压缩的时间线(都是 wire 里的时间戳):后台任务启动后 ,full_compaction.begin 触发,位置在 turn 中途,模型未给出这一轮回复之前。压缩摘要由另一个 LLM 请求生成,花了 75 秒,而那个后台任务在之间的第42秒完成。
摘要应用的时候,摘要里面还写着"我刚派出后台任务,它完成时会自动通知我",但这句话已经过期了。
#2720 里已有人从二进制里扒出过一个保护逻辑,如果压缩期间循环追加了新历史,摘要会被丢弃(historySafeToCompact)。这个保护在本case没生效,因为后台任务完成不算"历史追加"。守卫只盯着对话历史,看不见任务状态和 TODO 的变化。
同一个问题还有个小变体:第五次摘要的正文说"根因已修复,子集全部通过",但末尾自动追加的 TODO 列表还写着"重跑 015/018/019 复现真实异常""按真实异常修 finalize 问题"。TODO 是压缩开始时快照的,原样拼在摘要后面,没有检查它和正文矛不矛盾。两条互相矛盾的指令装在同一条消息里。
一个结构性的观察。 顺手验证了一件事:压缩后第一个 LLM 请求的消息数。

第几次压缩 保留的用户消息数 压缩后首个请求的消息数
1 8 3
2 16 3
3 16 4
4 25 4
5 42 3

压缩前请求的消息数是一直涨的(压缩前已经到 342)。压缩后无论保留了 8 条还是 42 条用户消息,请求都只有 3、4 条消息。我在源码里找到了对应机制:投影层会把相邻的 user 消息合并(agent-core/src/agent/context/projector.tsmergeAdjacentUserMessages),而压缩重建后的历史恰好是一串连续的 user 角色消息(头段 + 省略标记 + 尾段 + 摘要),发到模型时被折叠成极少数几条。摘要因为 origin 标记不参与合并,是独立的一条——但仍然是 user 角色,排在整个上下文最末尾。
所以"用户消息逐字保留"这个说法有个问题:消息内容是保住了,但丢失了消息边界。用户的真实指令和模型摘要混在一起,摘要虽然因为 origin 标记保住了独立消息的身份,但它的角色同样是 user,而且排在整个上下文最新的位置。模型在这个上下文里看到的最后一段话,是它自己之前写的笔记,却以用户指令的角色和位置出现。

复现步骤?

无法在单次操作上确定性复现,这是一个间歇性的结构缺陷,与任务阶段、后台任务和摘要内容相关。三个出问题的session的共性模式:

  1. 长 session 跑到自动压缩触发(k3-256k 下约 20 万 token)。出问题的案例里,压缩都是在turn中途触发的:某个step刚结束、模型还没给出本轮回复。
  2. 满足以下任一条件:精确需求只存在于assistant/tool消息里(比如写进子agent派发命令的实现规格);摘要生成期间有后台任务在跑或TODO store在变化;存在与摘要所声称的"已批准"相冲突的用户常驻约束。
  3. 压缩后继续,对比压缩前后的行为。
    受影响的session id之一:session_069ea289-0ec3-493f-918f-6bbe14c8826b(5 次压缩,每次约 20 万 → 2.9 万 token,保留用户消息数 8/16/16/25/42)。另外两个压缩后偏离的session可按需提供。
    可以通过一个实验分离harness因素和模型因素:构造一个压缩边界,摘要里写"用户已批准 X",同时上下文里逐字保留用户消息"不要自己做 X",然后换不同的模型各跑若干次,统计覆盖率。所有模型都覆盖,就是harness的权威注入问题;只有特定模型覆盖,才有模型归因。
期望的行为是什么?
  • 摘要不应该和用户指令不可区分。用独立的内部role/包装注入,逐字保留的用户消息保持独立消息边界,并写明显式冲突规则:逐字用户消息优先级高于摘要,摘要里说"已获批准"永远不构成授权来源。
  • full_compaction.begin 和apply之间如果有任何变化(上下文版本、后台任务状态、TODO store),丢弃摘要重新生成。这能堵上之前出现的过期窗口,并把 historySafeToCompact 扩展到非历史状态。
  • TODO 列表在apply时从store现取,别用开始时的快照,或者至少和摘要正文做一次一致性检查。
  • 原样保留进行中的turn(其assistant/tool消息,包括原始工具参数),精确规格和验收条件就在这些消息里。摘要本身也应该从散文改成结构化的恢复块:当前请求、任务阶段(包括"还欠用户一次汇报")、验收条件逐条列、已授权动作引用用户原话、禁止动作、待用户决定事项、在途任务状态、已验证事实 vs 未验证假设。
  • 对push、合并、部署、改分支保护、删数据这类对外不可逆的操作,压缩边界应使摘要派生的授权失效:压缩后权限层要求授权必须来自上下文里仍在的逐字用户消息,找不到就停下来重新问。
补充信息

归因:harness 还是模型?
案例一里,模型在决定禁用 ruleset 之前的思考过程其实看到了用户约束,它原话引用了"尽可能不要自己决定",也承认"这正是那种出乎意料的阻塞""修改分支保护是安全相关的变更"。但最后两个 harness 注入的内容把它推向行动:一是交接笔记里那句"已获批准,按上面执行即可",以 user 消息的身份压在上下文最新位置;二是auto permission mode的系统提醒,明确写着"不要问用户,自己做个合理决定继续"。模型在两个互相冲突的指令之间选了错的一边,这是模型的判断失误;但它引用的两个"放行理由"全都是harness给的。一个完美对齐的模型应该停下来汇报,但harness的设计让"继续"成了阻力最小的路。
案例二更偏向 harness。原始规格只存在于被压缩掉的工具参数里,压缩后上下文里没有任何可以核对的真相,它只能根据摘要里的一句话概括重新发明一份。模型的问题在于另一个地方:它把这份重新发明的规格当成原任务直接执行了,没有向用户标注"我手上的规格是重建的,可能不完整"。包装里"当笔记别当证据"的提醒它也忽略了。所以模型的提升无法治本,核对所需的原材料已经不在上下文里了。
案例三(34秒过期快照、TODO 与正文矛盾、消息折叠)则完全是 harness 的,模型连参与的机会都没有。
和已有 issue 的关系。 #2680 报的是压缩后 agent 跑题——摘要没交代清楚哪些旧请求已经做完了,模型把一条早就完成的指令当成新任务。那是"摘要里缺东西";本 issue 是"摘要里多出来的东西被当成了什么",是它的镜像,但修法不一样:给旧请求标状态解决不了授权被自我扩张的问题。#2720 是压缩commit时的并发竞态,和案例三共享同一段代码路径,两份报告可以互证。#2622 里有人凭经验猜过"高上下文填充后 agent 会把摘要里没验证过的'已完成'当真",但没找到机制;案例一和二算是给这个猜测补上了证据。#3361 要求的pinned facts和保留预算,方向和上面的建议一致。
统计。 检查了 17 个 session,6 个发生过压缩,共 20 次,平均约 193K → 29K token(保留约 15%)。3 个 session 出现明确的压缩后偏离,其余续接正确。
原始 session 里有项目私有数据,公开 issue 就不附了。脱敏的wire摘录(事件时间线、摘要原文、消息数统计)如有需要可以通过/feedback和邮件提供。

Contribution
  • 我愿意自己提交修复此 bug 的 PR(请先等待维护者在本 issue 中批准)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with agent-core/src/agent/context/index.ts and its applyCompaction path, then read compaction/handoff.ts, v1 full.ts, v2 fullCompactionService.ts, and v2 historySafeToCompact. Inspect projector.ts and mergeAdjacentUserMessages for the post-compaction message shape. Done means the reported authorization, lost-specification, stale-state, TODO-conflict, and in-flight-turn cases are addressed with coverage for the relevant compaction behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
authorization, backend, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.