第二阶段 · 可靠的本地编码 Agent第 7 / 30 章
本章目录

第 07 章 · Shell、PTY 与后台进程

本章目标:看懂“运行一条命令”背后的进程模型。测试命令、交互式 CLI 和持续运行的开发服务器,需要不同的执行与停止机制。

1. Shell 工具为什么强大也危险

Shell 能调用几乎所有本地能力:测试、Git、构建、包管理、网络和系统命令。给 Agent 一个 run_shell,等于给它一把通用工具,同时扩大了命令注入、路径越界、秘密泄漏和长时间占用资源的风险。

Executor 至少要固定工作目录、显式管理环境变量、设置超时、捕获 stdout 与 stderr、保留退出码,并根据权限策略决定是否询问。

2. 一次性进程的完整结果

对于 pytest、git diff 或编译命令,可以等待进程退出,再返回:

字段 用途
command 审计实际运行了什么
cwd 防止模型误判工作目录
exit_code 区分成功、业务失败与信号终止
stdout / stderr 提供下一轮观察
duration 发现异常缓慢
timed_out 区分失败和被框架停止

不要只看 stderr 是否为空。很多测试框架把正常信息写入 stderr,也有程序失败时只写 stdout。退出码是第一信号,文本是诊断材料。

3. PTY 解决什么问题

有些程序检测自己是否连接终端,并使用颜色、进度条、密码提示或交互菜单。普通管道与真实终端行为不同。PTY,也就是伪终端,让宿主程序可以像人类终端一样与进程双向通信。

Agent 需要 PTY 的典型场景包括交互式调试器、需要确认的安装器、长时间开发服务器和 REPL。此时工具调用不应等到进程结束才返回,而是创建 session_id,后续通过 write_stdin 和 poll 继续交互。

4. 后台进程需要生命周期

启动开发服务器后,run_shell 不能永远阻塞。Runtime 应保存进程 id、启动命令、端口、最近输出和状态。任务结束、中断或切换工作区时,要决定保留还是终止。

典型接口是:

start_process(command) → process_id
poll_process(process_id) → new_output + status
write_stdin(process_id, text)
terminate_process(process_id)

这与模型调用循环是两个不同循环:Agent turn 在推进任务,后台进程在操作系统中持续运行。混在一起会造成终端残留和无法结束的任务。

5. 输出治理

持续日志会迅速塞满上下文。Executor 应只返回“自上次 poll 以来的新输出”,并限制字节数。完整日志存磁盘,模型需要时按范围读取。对 ANSI 控制码、动态进度条和重复行进行清理,也能显著减少噪声。

错误摘要不能完全取代原始证据。可以同时返回 summary 和 log_path:模型先读摘要,诊断不足时再读取原始片段。这就是渐进式上下文加载在进程工具中的应用。

6. 安全与代码落点

Shell 命令最好以参数数组执行,避免不必要的 shell 字符串解释;确实需要管道和重定向时,要提升风险等级。环境变量应使用允许列表,避免把所有宿主秘密传给子进程。

典型事故

Agent 启动服务器后又启动第二个实例,因为它没有记录第一个进程仍在运行。端口冲突只是轻微后果;更糟的是重复启动迁移、队列消费者或外部同步任务。

把 run_shell 扩展成可管理进程时,新增的不是一个参数,而是进程注册表、增量输出、终止策略和会话恢复规则。