Agent 是怎么工作的:深入理解上下文窗口
LLM 是大脑,harness 是身体;上下文窗口是大脑每次行动前重读的现场白板。
LLM / Harness
讨论 agent,先把大脑和身体分开:
上面一行是 model,负责读文字、写文字;下面一行是 harness,负责把 model 写出的 tool call 变成真正的动作。
所以 GPT-5.5 或 Opus 4.8 本身不会读文件、跑测试、改代码、打开网页,也不会访问 GitHub、Slack 或数据库;Codex、Claude Code、OpenClaw、Hermes、Cursor 这类 harness,才会把工具说明书放到模型面前,接收它写出的调用,替它执行,再把结果写回去。
模型每一次判断下一步,都只看 harness 递给它的一份材料。
这份材料,就是上下文窗口。
上下文窗口,就是模型眼前的现场白板
Context window 是 harness 每次调用 LLM 前,递给它的现场白板。
当前任务、系统规则、开发者指令、工具说明书、历史对话、读过的代码、搜索结果、命令输出、patch 记录、测试结果、截图、思考痕迹,都写在这张白板上。
任何东西想影响模型的判断,都必须先进入 window:搜索代码的结果、读取文件的内容、patch 的结果、测试输出、截图和浏览器反馈,都是这样。
LLM 每一次“想下一步”,都在重新读取这张现场白板。它没有别的记忆,只有眼前这份文件。
Agent Loop:想一步,做一步,看结果
我们分析了本地 191 个真实的 Codex session 日志。每一个 turn,都是同一套心跳:
task_started ← 你按下回车 user_message ← 你的话进入 window reasoning ← 它先想 function_call ← 它“描述”想跑的命令 function_call_output ← 身体执行完,结果塞回 window reasoning ← 再想 function_call ← 再干 ... (循环 N 次) agent_message ← 它向你汇报 token_count ← 记账 task_complete ← turn 结束
注意 function_call 这个词。模型没有跑命令,它只是写下了“我想跑这条命令”。真正按下执行键的是 harness。执行结果再作为 function_call_output 贴回白板,模型读到结果,继续想下一步。
这就是 agent 和普通 chatbot 的全部区别。单次问答是闭卷考试:一次性给出答案,对错都无法验证。Agent Loop 是开卷实验课:想一步、做一步、看结果、再想。
在我们统计的 19 万条日志事件里,function_call 出现了 28,341 次,reasoning 出现了 16,387 次。每一次,模型都把白板从头读一遍。
Agent 的五类器官:看、改、反馈、记忆、连接
Agent 的身体由五类器官组成:眼睛负责感知,手负责改变世界,神经负责把结果传回来,海马体负责记忆和压缩,触手负责接入更多外部系统。
这些器官本质上都是 harness 暴露给模型的工具。模型用文字写出调用,harness 执行动作,再把结果写回上下文窗口;下一轮,模型读着这张更厚的白板继续判断。
我们把真实 session 里的工具调用归类,正好落在这五个器官上。
器官一:眼睛(感知世界)
每次“看”,看到的内容就贴进 window。眼睛没有眨眼功能:看过的东西不会自动消失。
| item | 作用 | 实测 |
|---|---|---|
| exec_command → cat/sed/ls | 读文件 | 23,630 次调用中的读类 |
| exec_command → rg/grep/find | 搜代码 | search 类输出 510K tokens |
| view_image | 看截图 | 208 次 |
| web_search_call | 查网页 | 532 次 |
| input_image(用户粘贴) | 接收你给它看的东西 | 每张约 4,000 tokens |
器官二:手(改变世界)
手每动一次,window 里就多一份“这次我写了什么”的记录。改 10 次同一个文件,window 里就躺着 10 个版本。
| item | 作用 | 实测 |
|---|---|---|
| apply_patch | 唯一的代码写入通道 | 4,602 次 |
| exec_command → git/npm | 提交、装包、跑构建 | — |
| write_stdin | 给运行中的进程喂输入 | 2,234 次 |
| js | 跑一段 JavaScript | 1,067 次 |
器官三:神经(反馈回路)
眼睛和手只是出招。真正让循环成立的,是每一招都有回音。
| item | 作用 | 实测 |
|---|---|---|
| function_call_output | stdout、stderr、exit code | 23,630 次 exec,每次一份 |
| 测试 / 构建输出 | 改动对不对,由它裁判 | exec 输出的主力 |
| agent_message ↔ 你的回复 | 人类回路:它汇报,你纠正 | agent_message 15,049 次 |
| token_count | harness 的记账 | 每 turn 一次 |
器官四:海马体(记忆系统)
工作台堆满以后,compaction 会把桌面扫进一个加密压缩包,腾出地方继续干。但压缩包里有什么,它自己也看不清了。
| item | 作用 | 实测 |
|---|---|---|
| 上下文窗口本身 | 短期记忆,也就是工作台 | 上限约 272K |
| update_plan | 贴在白板上的任务便签 | 313 次 |
| compaction | 记忆压缩,全部 encrypted_content | 4 次 / session |
| EchoMem 系列工具 | 跨 session 长期记忆 | 765 次调用 |
器官五:触手(无限扩展 = MCP)
工具的本质只是一段 JSON Schema 文字。给 agent 加一个新能力,不需要重新训练模型,只需要往 window 里塞一段说明书。
| item | 作用 | 实测 |
|---|---|---|
| MCP 文档工具 | 读写 Google Docs 等 | _get_document_text 等 7 个 |
| MCP Slack 工具 | 查 Slack | 5 个 |
| MCP GitHub 工具 | 搜 repo / issue | 3 个 |
| spawn_agent / wait_agent / close_agent | 召唤分身 | multi-agent |
| tool_search_call | 工具太多时,先搜工具 | 233 次 |
真实一轮:你的指令怎么变成下一次行动
理论说完了,逐帧回放一个真实 turn。Erik session,turn 74,你说:
For mobile, let's just do a simple vertical scroll experience
第 1 帧:你说话。 一条 user message 进入 window。
第 2 帧:看清此刻 window 里已经装着什么。 这一轮的输入是 228,649 tokens,其中 219,520 是缓存重放的旧内容,本轮新鲜内容只有 9,129。
也就是说,你的这句话漂浮在一片历史海洋上,新内容只占窗口的 4%,另外 96% 是历史:之前 73 个 turn 攒下的文件读取、命令输出、patch 记录、加密的思考残迹,以及 4 次 compaction 留下的压缩包。
第 3 帧:循环开始。 大脑想,眼睛搜 rg "mobile",眼睛读相关组件,手改 apply_patch,神经把每一步的结果传回白板,大脑再想,手再改,最后通过人类回路发出汇报,等你的下一句话。
第 4 帧:每一步都留了痕。 这一轮搜过的结果、读过的文件、打过的 patch、传回的每一声回音,全部永久贴进 window,成为 turn 75 那 226,660 tokens 历史的一部分。
一个大脑和它的器官各动了一次,而每一次动作,都让下一次心跳的“眼前文件”更厚一点。
代价:每个动作都会让窗口更厚
现在把五幕连起来看:五大器官的每一次动作都留痕,而且默认不会清除。
于是在我们逐 token 归因的 session 里,window 中 63.1% 是废物:眼睛看过的旧截图占 17.2%,手跑过的旧命令输出占 24.3%,读过又过期的死文件占 15.1%,而真正“活着”的文件阅读只有 6.4%。
你的 agent 不是不聪明,它是被自己的历史压弯了腰。
下次开工,用这副 X 光眼镜检查窗口
下次开 session,带着这几条看:
- 你粘贴的每张截图约 4,000 tokens,且会留在 window 里。
- 你的 agent 每次回答前,都在重读整个 window。
- compaction 不是清理,是打包进一个看不清的压缩袋。
- 想让 agent 跨 session 记住事情,需要外部记忆:EchoMem 或 memory 工具。
它没有魔法。它只是一个不停重读全文的大脑,五个忠实记录一切的器官,和一张越堆越满的工作台。看懂了这三样,你就看懂了你的 agent。
数据来源:context-golden-standard 项目对真实 Codex session 的逐轮解剖。事件计数来自 JSONL archive(~/.codex/sessions),payload 逐项 token 计数来自 SQLite log(~/.codex/logs_2.sqlite)的 response.create 请求体;窗口占比来自 retained-window 重建与 SESSION_WASTE_DASHBOARD 数据集。
下一篇:你的 context window,到底有多脏?
看懂了 agent 怎么工作,下一步就该打开那张越来越厚的工作台,看看里面到底多少 token 还在干活,多少只是历史沉积。