> For the complete documentation index, see [llms.txt](https://levon.gitbook.io/agent-engineering/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://levon.gitbook.io/agent-engineering/14agent-yun-xing-kong-zhi.md).

# 第 14 课：Agent 运行控制——并发、预算与资源释放

假设助手已经修改配置，接着启动测试。日志一直滚动，你点击停止，界面也显示“正在取消”，电脑却仍然很忙。再启动一个任务，机器反而更慢了：旧测试的子进程还在跑，新任务又占了一份资源。

[第 10 课](/agent-engineering/10agent-bian-pai.md)安排了“修改、验收、失败后修复”的步骤。可是步骤排好了，还有三件事没人管：

1. 一个动作**能不能开始**：有没有空出的名额，额度还够不够？
2. 开始以后**能跑多久**：时间到了谁叫停？
3. 停下以后**资源是不是真的还回来了**：进程结束了吗，名额能给下一个吗？

这些限制都由模型之外的程序来执行，合起来就是本课说的**运行控制**。下面沿着同一个配置任务，按“开始前、运行中、停止后”的顺序讲。前置只需要第 3 课的工具循环和第 9、10 课的程序验收；第 12、13 课不是必修前置。

## 1. 开始前：哪些调用可以一起执行

模型要比较 `a.md` 和 `b.md`，可以在同一次回复里申请两次读取。两个读取互不依赖，程序可以同时执行，收齐内容后再请求模型比较。

修改配置就不一样了。假设写入工具要求提供完整的新文件内容，模型不知道原来的字段，就得先读：

```
模型申请读取 -> 工具返回原文 -> 再次请求模型 -> 模型生成写入调用
```

中间那次模型请求省不掉。如果模型在同一条回复里同时申请了读取和写入，写入的参数在读取之前就已经定好了；程序把两者改成先后执行，也不会让写入参数自动用上刚读到的内容。

框架通常只提供“整批串行”或“整批并行”的开关，不会替你分析依赖。例如 Pi 在全局配置为串行、或本批有工具声明需要串行时整批顺序执行，否则并行。\[1] 哪些调用必须等模型看过新证据再生成，要由应用安排。

## 2. 开始前：同时跑几个，多久发起多少次

假设一个抓取服务的账号最多允许三个请求同时运行。模型即使提出二十个互不依赖的抓取，也只能先执行三个，结束一个再补一个。这限制的是**并发数**。

如果服务还规定每分钟最多三十次，请求完成得再快也不能无限补发。并发名额随请求结束归还，但这一分钟里已经发起的次数不会消失。这限制的是**请求频率**。

两者要分别管：

| 限制   | 管什么          | 名额什么时候还回来 |
| ---- | ------------ | --------- |
| 并发数  | 同一时刻最多有几个在运行 | 这项工作真正结束时 |
| 请求频率 | 一段时间里最多发起多少次 | 时间窗口过去时   |

同一个进程里，可以用信号量表示三个可占用的名额。注意，队列容量为三只限制等待区能放几项，并不保证最多只有三个在运行。多个进程共用一个账号时，每个进程各限三个，合起来仍会超；需要一个覆盖所有进程的统一入口。

能不能并行，和最多并行多少，是两个判断。Pi 的并行路径等整批结果，本身不设最大同时执行数；这个上限要由工具、外部服务或应用另外加上。\[1]

## 3. 开始前：模型请求和工具执行分别计数

回到第 10 课的配置任务：主题改成 dark，端口保持 3000。固定剧本第一轮误改端口，第二轮再修复。第一轮有三次模型请求（申请读取、申请写入、根据结果回答），但实际执行的工具只有两次。

所以两种次数要分开记。程序拒绝一个工具调用时，工具没有运行，但模型生成这份申请的那次请求已经发生了。只限制工具次数，模型就可能不停换个工具申请，程序不停拒绝，循环停不下来。

现在给整个任务三次工具额度，跨修复轮次共享。实际轨迹是：

| 调用    | 实际结果               | 剩余工具额度 |
| ----- | ------------------ | ------ |
| 第一轮读取 | 读取原始配置             | 2      |
| 第一轮写入 | 写成 dark，端口误改为 8080 | 1      |
| 第二轮读取 | 读取当前错误配置           | 0      |
| 第二轮写入 | 额度不足，拒绝执行          | 0      |

文件停在 `theme=dark`、`port=8080`，验收 failed，任务停为 `tool_budget_exhausted`。把额度改为四次，固定剧本就能完成修复；余额恰好为零，也不妨碍任务通过验收。

检查必须发生在**每一次实际执行之前**。如果一批有三个调用、只剩一个名额，却等整批跑完才检查，后两个就拦不住了。框架一般会提供这样的检查位置，例如 Pi 的 `beforeToolCall` 可以在执行前阻止调用。\[1] 具体预算怎么定，仍由应用决定。几个调用并行争抢最后一个名额时，检查和占用还必须放在同一个受保护的操作里。

## 4. 运行中：截止时间与收尾

次数有限，不代表时间有限。三条命令各跑四分钟，每条都遵守“单次五分钟超时”，整个任务也已经用了十二分钟。所以除了单次超时，还要给**整个任务**设一个截止时间。

剩余时间要在真正开始时重新算。比如工具入队时还剩四十秒，排队等名额用了三十秒，真正启动时就只剩约十秒；已经到期就不再启动。进程退出后换别的程序接手，也不能把同一个任务的截止时间往后延。

停下之前，还要决定怎样向用户交代进度。一种做法是在模型总预算里预留一次“收尾请求”：程序把原始任务、已有的调用与回执、最新验收结果交给模型，让它说明做到了哪里。收尾请求不提供工具，模型就算申请写文件，程序也不会执行。

预留是有代价的，它会占掉一次推进任务的机会。同一个固定剧本，模型总预算都是五次：

| 策略   | 最后一次推进到哪里               | 文件评分   | 收尾结果          |
| ---- | ----------------------- | ------ | ------------- |
| 不预留  | 第五次请求完成第二轮写入，端口恢复为 3000 | passed | 没有第六次请求取得最后回答 |
| 预留一次 | 前四次只来得及第二轮读取，端口仍为 8080  | failed | 第五次请求说明任务未完成  |

两种情况下任务都没有正常完成。收尾请求成功，只说明模型交回了一段说明，不能把任务改记为成功，也不能证明说明准确。所以最终的结论由程序根据任务状态、评分和额度来报告，模型的说明只作参考。收尾本身也要预留时间，总截止时间到了，不能为了写总结继续超时运行。

## 5. 停止后：确认工作结束，再还名额

回到开头：点了停止，电脑还在忙。问题出在“发出取消”和“工作真的结束”是两回事。

程序发出取消信号，要靠工具自己响应。Pi 的 `agent.abort()` 会把取消信号交给正在执行的工具；如果工具忽略它、一直不返回，取消请求并不能强行结束这段等待。\[1]\[2]

本地命令还有一层麻烦：一条测试命令可能启动很多子进程，只结束外层的 Shell，实际干活的子进程还在。Pi 的本地命令工具收到取消后会终止整个进程组，再等子进程退出。\[3]\[4] 即便如此，自行脱离原进程组的后代进程也不一定能被一起清理。

这正是开头机器变慢的原因。三个并发名额里，A、B、C 在运行，D 在等。向 A 发出取消后，如果马上把名额让给 D，而 A 其实还在抓取，实际运行的就变成了四个。**名额要跟着它代表的实际工作走**：确认工作结束（取消成功或正常完成）后才释放，界面停止等待不算。

![A 已贴上“已发取消”却仍拖着子进程工作，小黑守在门口握住名额牌，不交给门外等待的 D](https://3256184984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO56Bk11JpENsvGCw6xhb%2Fuploads%2Fgit-blob-ee209e64ad701008998455673f8ec22b5ac42292%2F01-slot-follows-work.png?alt=media)

远端任务也一样。关闭本地连接，不能证明远端构建已经停止。要保存原任务编号，按服务接口确认状态，再处理对应资源。第 10 课讲过等待、取消和补偿在业务上的区别；本课补上的是：停止信号怎样真正落到执行端，资源何时才能重新使用。

## 6. 停止后：控制输出，如实报告

运行中还要控制工具返回多少内容。测试产生两万行日志，全部塞进模型输入会挤占大量上下文。Pi 的命令输出默认最多返回 2,000 行或 50 KiB，先到哪个按哪个截断，保留末尾，并给出完整输出所在的临时文件路径。\[5]\[6]

路径只是查阅入口。如果末尾没有真正的错误，Agent 可以搜索完整日志，再读取匹配位置附近的内容。没读到前面的日志，就不能把局部内容当成整份证据，这接着[第 5 课](/agent-engineering/05-shang-xia-wen-gong-cheng.md)对原始材料和本轮输入的区分。

返回正文有上限，不等于磁盘有上限。完整日志仍会写进文件，这个组件并不限制文件总大小。\[5] 如果应用另设了磁盘额度并且被触发，就应停止继续保存、请求终止命令、确认相关进程已结束，并把已有日志标为不完整。

回到开头的修复任务，最终报告要分清已经发生的动作和没有拿到的证明。例如：“配置已经修改；测试因日志额度耗尽而中断，相关进程已停止；日志不完整，尚不能确认测试通过。”程序可以据此决定是否需要用户介入，而不会把“停止机制运行正常”误写成“原任务完成”。

## 配套实践

[进入第 14 课实践](https://github.com/unix2dos/agent-engineering-book/tree/main/practice/lesson-14/README.md)：先预测三次工具额度会挡住哪个调用，再对照预留收尾前后的文件、评分和停止原因。实现复用第 10 课的配置工作流，不需要复制另一套 Agent；收尾实验的开关、报告字段和终端输出规则都在实践说明里。

## 依据与版本

Pi 源码固定到提交 `e266507b606b9552fa277252644054afd4384b11`，正文用它解释机制；文中的并发数、频率和时间数字是教学条件，不是 Pi 的默认配置。核验范围、实践结果和源码指纹见[本课证据记录](https://github.com/unix2dos/agent-engineering-book/tree/main/research/14-runtime-control-source-verification.md)。

配套工作流已经验证工具次数额度和无工具的收尾请求，模型回复使用固定剧本。共享并发、请求频率、总截止时间、进程回收与日志磁盘额度在本章通过源码和场景解释，尚未集成到这条工作流；工作流是单写者检查点，没有验证并发原子扣减或自动重启续跑。这些检查不能证明真实模型的能力或完整生产可靠性。

1. [Pi：工具调度、执行前检查与轮次停止](https://github.com/earendil-works/pi/blob/e266507b606b9552fa277252644054afd4384b11/packages/agent/src/agent-loop.ts)
2. [Pi：Agent 取消信号](https://github.com/earendil-works/pi/blob/e266507b606b9552fa277252644054afd4384b11/packages/agent/src/agent.ts#L313-L321)
3. [Pi：本地命令的超时与取消处理](https://github.com/earendil-works/pi/blob/e266507b606b9552fa277252644054afd4384b11/packages/coding-agent/src/core/tools/bash.ts#L83-L150)
4. [Pi：进程组终止与回退](https://github.com/earendil-works/pi/blob/e266507b606b9552fa277252644054afd4384b11/packages/coding-agent/src/utils/shell.ts#L216-L247)
5. [Pi：日志尾部收集与完整输出文件](https://github.com/earendil-works/pi/blob/e266507b606b9552fa277252644054afd4384b11/packages/coding-agent/src/core/tools/output-accumulator.ts)
6. [Pi：输出正文的行数与字节上限](https://github.com/earendil-works/pi/blob/e266507b606b9552fa277252644054afd4384b11/packages/coding-agent/src/core/tools/truncate.ts#L1-L11)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://levon.gitbook.io/agent-engineering/14agent-yun-xing-kong-zhi.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
