Skip to content

fix(agent-core-v2): cap Read at about 25k tokens and trim file tool results - #4553

Merged
RealKai42 merged 4 commits into
release/2026-10-30from
fix/read-cap-release-2026-10-30
Oct 30, 2026
Merged

RealKai42 merged 4 commits into
release/2026-10-30from
fix/read-cap-release-2026-10-30

Conversation

@RealKai42

@RealKai42 RealKai42 commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Requirement or Bug

Read 读进来的内容会一直留在上下文里,在压缩前每一步都随请求重发。但单次 Read 的上限只按字符计,模型最多能申请 500,000 字符;同样 100,000 个字符,中文按 Kimi Code 自己的估算是代码的 4 倍 token。这个 PR 把单次 Read 限制在约 25k token,并去掉 Read、Edit、Write 结果里不必要的文本。

Bug Reproduction Steps

  1. 默认配置下,让 agent 不带 n_lines 地 Read 一个十几万字的中文 Markdown 文件。
  2. 改动前:一页返回约 100,000 个字符,按 estimateTokens 约 100k token;模型如果按描述的建议带上更大的 max_chars,一次最多返回 500,000 个字符。
  3. 改动后:一页约 25k 估算 token,Next Read 继续。

Root Cause

  • 单次读取的预算只按字符(JS 字符串长度)计,没有 token 维度:非 ASCII 字符约 1 token,ASCII 字符约 0.25 token。
  • 模型可申请的上限是 500,000 字符,read.md 还要求「需要全文时第一次就申请更大的 max_chars」。
  • 每次 Read 都附带约 150 字符的状态块,即使一次就读完了整个文件。
  • Edit/Write 成功后没有任何说明,模型容易为了确认改动而把文件读回来。

前两点是预算的根本修复;后两点降低每次结果的固定开销和回读倾向。

Code Changes

核心思路:保留现有按字符分页、Next Read 续读和长行分片的全部机制,在组装每一页时多加一个估算 token 的约束(算法与 estimateTokens 一致):超了就在当前页上收缩,多行丢行,单行按 token 找切点,不重读文件。上限是「字符上限 ÷ 4」向上取整,纯 ASCII 页面永远碰不到它,原来的字符分页不变;中文页面从约 100k 字符缩到约 25k 字符。指定了范围的读取(带 n_lines 或负的 line_offset)先照旧按字符上限收缩;如果字符上限内放得下的行仍超过 token 上限,就不返回其中一部分,而是直接报错并建议一个放得下的范围,避免模型把半截范围当成完整范围。只被字符上限截短的范围(例如大段代码、日志尾部)、整文件读和只给起始行的读取,仍然截短后翻页。结果文本也同步瘦身:整文件读完时不附状态块,没读完时提示可以用 Grep 定位,Edit/Write 成功后告诉模型不需要回读。

 execution(source, args, eventLog)
   limits = [read] config ?? { default: 100k, max: 100k (was 500k) }
+  request = { maxChars, tokenLimit: ceil(max / 4) }
   return readForward or readTail(request)

 finishPage(page)
   loop
-    fits = renderedChars <= maxChars
+    fits = renderedChars <= maxChars && estimatedTokens <= tokenLimit
-    note = <system>summary + notices</system>
+    note = whole file && no notices ? none : <system>summary + notices</system>
+    if a named range fits maxChars but still overflows tokens: return an error that suggests a range that fits
     if fits: return page
+    if only tokens overflow: drop enough lines at once, or cut a single line by token weight
     otherwise: drop one line, or cut a single line by characters (unchanged)
   if incomplete
     Next Read { max_chars: maxChars }
+    "If the answer may be further on, continue with Next Read or find the section with Grep instead of answering from this page alone."

token 约束和字符约束在同一个组装循环里生效:只有字符超限时,仍按原来的方式一次丢一行或按字符切分;token 超限时,一次丢够行或按 token 切分(指定范围的多行页不走这一步,而是报错)。所以不需要重读文件,一次就满足上限;从尾部读一个放不下的长行时,会落到原有的「正向续读」恢复路径。

决策 选择 没选 理由
token 上限怎么落地 组装这一页时按估算 token 收缩:丢行或按 token 切单行,不重读文件 读完后缩小字符预算再重读 一次就满足上限,不会因为预算远大于文件而收不下来;从尾部读放不下时走原有的恢复路径
token 上限的取值和开关 max_chars ÷ 4 向上取整,随 [read] max_chars 一起调 新增 max_tokens 配置项 纯 ASCII 页面永远碰不到这个上限,原来的字符分页不变;用户只需要一个旋钮
指定范围超过 token 上限 先按字符上限收缩;字符放得下而 token 仍超时返回错误,写明一次最多读几行,并给出可直接使用的参数 截短后给 Next Read;或者整个范围一超 token 就报错,不先按字符收缩 模型按自己的计划翻页时,容易把半截范围当成完整范围,漏掉中间的行;报错不会被误读。纯 ASCII 页面字符放得下就一定在 token 上限内,所以只被字符截短的范围照旧翻页,原有行为不变。只请求一行或接着某个长行的列偏移继续读时,仍然分片
被 token 上限缩短时要不要说明原因 不说明,照常给出 Next Read 在状态块里解释 模型下一步的做法不变,解释不改变任何决策
Edit/Write 的提示放在哪 note,只给模型看 追加到 output UI 不展示 note,确认行的展示不受影响
超大文件 只加 token 上限 按文件大小直接拒绝整文件读取 token 上限已经把单页成本限定在约 25k,直接拒绝还要多一次往返

阅读指南:

  1. packages/agent-core-v2/src/agent/tools/os/read/readTool.ts:finishPage 里的字符和 token 双约束,以及状态块拆成 summary 和 notices 后的组装。
  2. packages/agent-core-v2/src/llm-adapter/contract/tokens.ts:拆出的计数函数,estimateTokens 的结果不变。
  3. packages/agent-core-v2/src/agent/tools/os/read/read.md 的两处改动,以及 editTool.ts、writeTool.ts 的 note。

其余是测试和中英文文档。

Behavior Changes and Affected Users

影响范围:所有带 Read、Edit、Write 工具的 agent(主 agent 和子 agent),所有客户端(TUI、print 模式、经 kap-server 的 desktop 和 web、SDK),所有 provider。

行为 之前 之后 谁依赖旧行为 退路
单次 Read 可申请的 max_chars 上限 500,000 100,000 需要一次读完超过 10 万字符文件的任务 [read] max_chars
单页 token 上限 无,只按字符 约 max_chars ÷ 4 估算 token(默认 25,000),超了在当前页上丢行或切分,照常给出 Next Read;从尾部读放不下单个长行时返回正向续读提示 读中文等非 ASCII 大文件、希望一页拿到 100k 字符的任务 调大 [read] max_chars
指定范围(n_lines 或负的 line_offset)在字符上限内放得下的行超过 token 上限 只按字符截断:字符上限内完整返回,超出时截短并给 Next Read 返回错误,写明一次最多读几行和具体参数;只请求一行时仍然分片。只被字符上限截短的范围照旧截短翻页 一次读取大段中文范围的任务 调大 [read] max_chars
一次读完整个文件时的状态块 总是附带 没有需要说明的情况时省略 解析状态块的下游(仓库内没有,TUI 也不解析) 无,文件内容本身不变
没读完时的状态块 Next Read Next Read 加一句:答案可能在后面时用 Next Read 继续或用 Grep 定位,不要只凭这一页作答 无 无
读 wire.jsonl 事件日志时的备注 "increase max_chars for a longer record" "follow Next Read for the rest of a longer record" 读事件日志排查问题的场景 无
Edit/Write 成功结果 只有确认行 确认行,加只给模型看的 note:"The change is applied; there is no need to Read the file back to check it." 无 无;TUI 照旧隐藏确认行

Read 工具描述的改动(所有带 Read 的 agent 每次请求都会收到),逐句:

句子 改动前约束了什么 现在由什么约束
"Output is <line-number>\t<content> per line, followed by a <system> status block that is not part of the file." 改为 "Output is <line-number>\t<content> per line. A <system> status block may follow; it is not part of the file." 状态块总是存在,且不属于文件 一次读完整个文件时省略;出现时仍不属于文件
删除 "When the task requires the full text of a large file, request a larger max_chars in the first call." 需要全文时第一次就申请大额度 上限等于默认值后这句自相矛盾;全文改由 Next Read 翻页取得,描述里续读的那条规则不变

测试覆盖:

  • 100k 上限和配置退路:新增「默认把申请的预算截到 100000 并给出 Next Read」的用例;原来一次读完 4621 行文档、读完长事件日志记录的用例改为在 maxChars: 500_000 的配置下跑,钉住调大配置后的旧行为。
  • token 上限:新增三个用例:非 ASCII 页面按估算 token 缩短且 Next Read 保留原预算;单个长中文行按 token 分片;从尾部读放不下的长中文行时返回有界的正向续读提示。原来验证「按字符不按字节计」的多字节用例改为在调高上限的配置下跑。
  • 指定范围:正向和尾部的中文指定范围超过 token 上限时报错,照报错里建议的参数重读能拿到完整的一页;同时超过字符上限的中文范围同样报错;只超过字符上限的 ASCII 范围(正向 n_lines 和尾部读取)照旧返回一页,并给出接得上的 Next Read。
  • 状态块省略:新增「整文件读完不附状态块、范围读仍附带」的用例;其余整文件读取用例的断言改为 note 为空。
  • 工具描述:描述用例改为检查没有残留的占位符、配置的上限会体现在描述里。
  • Edit/Write note:现有 edit、write 测试通过(它们只断言输出)。
  • 工具执行:读回落盘 Glob 结果的用例改为在 500k 配置下跑;读取超过 5 万字符的整文件时不附状态块。

验证:类型检查和 pnpm lint 通过,相关测试全部通过。agent-core-v2 全量测试中,单独重跑后仍失败的只有子 agent 超时用例(base 上同样失败)和 MCP OAuth 的两个超时用例;它们不涉及 Read,本 PR 没有改动相关代码。

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: issue must have a maintainer's /approve).
  • I have added tests that prove my feature works.
  • The behavior-change table above is complete, and every removed behavior or flipped default is named in the changeset and either has an escape hatch or was explicitly approved by a maintainer in this PR.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

…esults

- Cap the max_chars a Read call can request at 100k characters (was
  500k) and drop the description sentence that told the model to request
  a larger budget for whole large files; the event log note now points
  to Next Read instead of a larger max_chars.
- Stop a page at about max_chars / 4 estimated tokens. The limit is
  applied while the page is assembled: drop whole lines, or cut a single
  long line by token weight, without re-reading the file. It rounds up,
  so pages of ASCII text never reach it and keep their exact character
  pagination. A tail read whose last line cannot fit returns the bounded
  forward recovery.
- Omit the Read status block when a call returns the whole file with
  nothing to report.
- On an incomplete page, point to Grep for a specific section instead of
  answering from that page alone.
- After a successful Edit or Write, tell the model it does not need to
  Read the file back.
@changeset-bot

changeset-bot Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e67be49

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T08:57:52.859277Z 605335c Manual request
🔒 Security Review ✅ Completed 2026-10-30T07:55:11.139131Z 174cf6a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@e67be49
npx https://pkg.pr.new/@moonshot-ai/kimi-code@e67be49

commit: e67be49

When a call names a range, with n_lines or a negative line_offset, and
those lines exceed the estimated token limit, return an error that says
how many lines fit and suggests the exact arguments, instead of a page
that silently covers only part of the range. Whole-file reads, reads
from an offset to the end, single-line requests, and column
continuations still page with Next Read.
@RealKai42

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c3b1eb7f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/agent-core-v2/src/agent/tools/os/read/readTool.ts Outdated
The named-range check ran before character trimming, so an ASCII range
longer than max_chars, whose status block pushed the page over both
limits, returned a range error instead of a page with Next Read. Run the
check only once the page fits max_chars, so it rejects a range only when
the token limit would cut it shorter than the character limit does.
@RealKai42

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 605335c1c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Every incomplete Read page now carries one more status sentence, so with
a 500-character budget and a long attachment path the status took about
490 characters and the read could not finish within the test's 80 pages.
@RealKai42
RealKai42 merged commit 9140ea0 into release/2026-10-30 Oct 30, 2026
25 of 26 checks passed
@RealKai42
RealKai42 deleted the fix/read-cap-release-2026-10-30 branch October 30, 2026 09:16
@github-actions github-actions Bot mentioned this pull request Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant