免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

多Coding Agent并行管理:Herdr与PTY终端运行时实践

多Coding Agent并行管理:Herdr与PTY终端运行时实践 最近身边的团队聊 Coding Agent 的越来越多从 OpenAI 的 Codex 到各类开源方案大家都在试着把写代码这件事交给“一个能自己动手的助手”。但真把 Agent 用起来之后你会发现单个 Agent 好用不代表多个 Agent 好用尤其在同一个项目里同时跑两三个 Agent它们可能改同一个文件、抢同一份环境变量、往同一个终端里吐日志整个状态非常混乱。于是就有了 Herdr 这类专门做“多 Coding Agent 管理”的工具。Herdr 的定位可以理解成一个调度器加运行时管理器给每个 Coding Agent 分配独立的工作空间和终端会话通过 PTY伪终端做底层状态管理让多个 Agent 能并行干活又不互相踩脚。这篇文章我会顺着“为什么要管多个 Agent → PTY 在其中扮演什么角色 → 终端运行时怎么落实 → 实际配置与排障”这条线把动手折腾 Herdr 的过程和踩坑记录都写出来。适合已经在用 Codex、pi coding agent 这类工具、希望在项目中并行引入多个 Agent 的开发者也适合还没用上 Herdr、但被多 Agent 并发问题困扰的朋友做个参考。1. 多 Agent 协作到底难在哪——为什么需要 Herdr 这种工具1.1 Coding Agent 的形态演变从单助手到多协作早期大家用的 Coding Agent本质上是把一个 LLM 包装成能操作代码库的工具链。你给它一个 issue它自己读代码、改文件、跑测试、提 PR整个过程像是一个“远程实习生”。这个阶段你只需要关心一个 Agent 的表现给它喂任务、等结果、检查输出就完了。但真实工程项目里单 Agent 的瓶颈很明显。一个稍微大点的需求往往要跨模块修改Agent 在单个上下文窗口里能看到的内容有限改到后面经常出现“前面改了 A 文件后面又按旧逻辑改 B 文件”的情况。于是大家开始尝试拆分任务前端模块一个 Agent后端模块一个 Agent测试补全再雇一个 Agent希望它们并行推进。想法很好可一旦真这么干问题马上就来了。这时候就轮到 Herdr 这类工具登场。它做的事情不是“再做一个更强的 Agent”而是把多个 Agent 当成多个可编排的“员工”来管理——给每个员工单独的工位、单独的工具箱、单独的任务清单再通过一个统一的控制台观察它们各自在干什么。这个定位听起来简单但真要在工程上落地涉及的东西比想象中多得多。1.2 多个 Agent 同时干活时的四大混乱现场我在实际项目里踩过的坑基本可以归成下面四类文件冲突。两个 Agent 同时 clone 同一个仓库各自基于自己的理解去改同一个文件。等两边都提交的时候后提交的那一个直接覆盖掉前面 Agent 的修改代码丢失连 git diff 都救不回来。即使在同一个分支上前面 Agent 刚重构完的接口后面 Agent 还在用旧接口调编译直接挂。环境变量与端口冲突。多个 Agent 要跑测试全默认监听 8000 端口第二个 Agent 一起来就报 Address already in use。更隐蔽的是环境变量污染一个 Agent 改了PYTHONPATH另一个 Agent 的子进程继承了改后的值明明代码没问题跑出来的结果就是不对。终端输出互相污染。多个 Agent 的日志同时刷在同一个终端里命令的回显和 Agent 的思考过程交叉混在一起根本分不清哪段输出是哪个 Agent 产生的。排查问题的时候对着混杂的日志发呆半小时是常有的事。资源竞争与编排丢失。模型推理本身吃 CPU/GPU多个 Agent 同时推理会导致互相变慢更麻烦的是任务编排手动在两个 Agent 之间分配任务一个 Agent 完成了另一个 Agent 还在等它的产出没有人做协调整个流水线就卡住了。这四个问题单独看都不难解决但合在一起就需要一个系统性的方案。Herdr 的答案很直接既然问题出在“共享”上那就把每个 Agent 的终端会话、工作目录、进程空间彻底隔离同时提供一个统一调度层来做管理和观察。而这个隔离方案的技术核心就是 PTY。2. PTY 为什么是多 Agent 管理的关键底座2.1 先搞懂 PTY 到底是什么PTY 全称是 pseudo-terminal伪终端。普通终端是一个硬件设备比如你面前的显示器加键盘而 PTY 是一对由内核提供的虚拟字符设备分为 master 端和 slave 端。程序连接到 slave 端时它看到的是一个“标准终端设备”可以读输入、写输出、控制光标、配置行列数。而 master 端由另一个程序控制可以往 slave 端写入数据模拟用户输入也可以读取 slave 端输出的所有内容。生活化类比可以把 PTY 理解成“一间为程序准备的虚拟办公室”。程序Agent走进这间办公室看到的是正常的办公桌、电话、白板——它以为自己在一个真实的环境里工作。但实际上房间的另一面是单向玻璃外面的人Herdr能看到它的一举一动还能通过内线电话向房间里喊话房间里的程序却完全不知道外面坐着谁。这套机制的核心奥秘在于很多程序的行为取决于“自己连接的是不是终端”而 PTY 让 Agent 连接到了一个“看起来是终端但不是真实终端”的地方。这就是为什么 PTY 成了 Agent 运行时的关键底座而不是简单地用管道把输入输出接起来。2.2 为什么管理 Coding Agent 离不开 PTY你可能会有疑问不用 PTY直接把 Agent 的标准输出重定向到一个文件再通过管道喂命令进去不也能实现“观察和操控”吗理论上可以但实际会碰一鼻子灰。一个很现实的问题是大量 CLI 工具一旦发现自己不是 TTY就会切换成非交互模式。比如docker compose up在非 TTY 下跑不会输出进度条和颜色再比如cd、export这类 shell 内建命令在非交互 shell 里行为也不一样更麻烦的是那些需要交互确认的工具在非 TTY 下往往直接报错退出或者默默以默认选项跑下去。Coding Agent 的本质恰恰是“一边看命令输出一边决定下一步做什么”。它需要命令输出尽量完整、格式尽量友好、交互尽量真实。如果输出被截断、进度信息丢失、颜色代码被剥离Agent 的判断依据就不完整最后生成的代码质量一定会打折扣。Herdr 选择用 PTY 而不是普通管道就是为了让每个 Agent 看到的命令行为和人类开发者在真实终端里看到的几乎一致。还有一个更关键的点PTY 天然支持终端尺寸调整resize。Agent 在渲染长输出的时候会根据终端宽度决定是否换行、是否截断。通过 PTYHerdr 可以主动设置窗口行列数让 Agent 拿到一个合理的展示空间这也避免了“输出被折行折得稀碎Agent 理解错上下文”的情况。实测下来同样的任务用 PTY 跑和用管道跑Agent 的出代码质量确实有明显差别尤其是涉及多步骤命令、需要观察中间输出的时候。3. 终端运行时Herdr 怎么把 Agent 变成可运行、可管理的进程3.1 终端运行时的三层结构知道了 PTY 是关键接下来要理解 Herdr 的“终端运行时”到底是怎么组织的。我在实际使用中把它拆成了三层来看这样排查问题的时候思路就清晰很多。第一层是进程管理层。每个 Agent 在 Herdr 里对应一个主进程以及由它派生的所有子进程。Herdr 会为这个主进程分配独立的进程组和会话让它的生命周期可以被单独控制。启动、停止、重启都是针对这一整棵进程树操作的而不是只杀掉顶层进程。这一点极其重要后面我会专门讲。第二层是 PTY 会话层。每个 Agent 的进程连接到一个独立的 PTY slave 端Herdr 持有对应的 PTY master 端。这一层负责三件事把键盘输入或者 Agent 的输入请求写入 master 端把 slave 端的所有输出读出来并存储实时感知终端尺寸变化并同步给 PTY。这一层做得够稳Agent 的输入输出才能真正“像一块终端”。第三层是会话恢复层。Herdr 会把每个 PTY 的输出流持久化为日志同时记录 Agent 的执行状态、工作目录、环境变量快照。这样一来即使 Agent 进程崩溃了或者整个 Herdr 重启了也能根据会话记录恢复现场重新拉起 Agent 而不丢失上下文。这层对长任务非常有用跑着跑着断网、机器重启恢复后还能接着干。这三层结构合在一起就是“终端运行时”的含义它不是为了跑某一个命令而是为每一个 Agent 提供一个完整的、可观察的、可恢复的运行环境。3.2 信号、会话和进程组运行时怎么做到“关掉一个不连累所有”在普通终端里同时跑多个命令时按 CtrlC 会把 SIGINT 信号发给整个前台进程组。如果两个 Agent 共享一个终端会话一个 CtrlC 会把另一个 Agent 的执行直接打断代码改到一半进程就没了非常打击士气。Herdr 的做法是给每个 Agent 独立的会话和进程组。会话session是进程组的上层集合一个会话里可以有多个进程组但一个会话通常关联着一个控制终端。通过setsid这个系统调用Herdr 让每个 Agent 变成一个新的会话首领session leader彻底脱离原来那个“共享终端”的进程组。这样一来外部信号就可以精确投递到目标 Agent而不会误伤其他 Agent。实际配置时Herdr 还可以设置每个 Agent 的停止策略优雅停止先发 SIGTERM等几秒再发 SIGKILL还是强制停止直接 SIGKILL。对 Coding Agent 来说优雅停止很重要因为 Agent 可能正在写文件突然被 SIGKILL 会留下半写状态的临时文件下次启动直接报错。我会把重启机制配合上max_restarts设成两三次防止因为一次偶发信号就挂掉整个任务。这就解释了为什么 Herdr 叫 “terminal runtime” 而不叫 “terminal launcher”。它管理的不是一个终端命令而是一整套进程生命周期、信号流转、会话隔离的运行时环境。理解了这一层再看它的配置项每个字段的作用就很清楚了。4. Herdr 的实际使用流程与配置要点4.1 安装与快速初始化Herdr 目前官方提供的是二进制分发包安装方式比较简单下载对应平台的压缩包解压后把可执行文件放到PATH里就行不需要额外的运行时依赖比如不需要单独装 Node 或 Python 环境这一点对部署环境比较友好。Linux 和 macOS 下基本是同样的操作流程Windows 下的兼容性目前还没完全覆盖建议在容器或者 WSL 里跑。安装完先执行一次初始化命令Herdr 会在用户目录下生成一个默认的配置文件目录里面包含配置模板和日志目录结构。我建议把配置模板打开看一遍每个字段都有注释清楚标明了作用比自己盲写配置强得多。一个最小可用的配置模板大概长这样基于 YAML 格式# herdr.yaml runtime: default_shell: /bin/bash log_dir: ~/.herdr/logs agents: - name: codex-worker-1 model: codex command: codex exec cwd: /workspace/project-a env: OPENAI_API_KEY: ${OPENAI_API_KEY} pty: enabled: true rows: 40 cols: 120 restart_policy: max_restarts: 3 - name: pi-agent-runner model: pi command: pi coding cwd: /workspace/project-b env: PI_API_KEY: ${PI_API_KEY}这里有几个字段值得展开讲command指定 Agent 启动时的实际命令。如果你是拿 Herdr 管理 Codex就填codex exec这类子命令如果你是管理 pi coding agent就填对应的pi coding。这个命令会在 PTY 里以交互模式启动所以确保 Agent 支持交互式 shell 的调用方式。cwd是每个 Agent 的工作目录强烈建议不同 Agent 用不同目录避免文件冲突。pty.enabled打开后Herdr 才为这个 Agent 分配伪终端关闭了就退化成普通管道模式一般不建议关闭。rows和cols是 PTY 的行列数默认 40x120 比较接近真实终端如果你的 Agent 经常输出超长日志可以把 cols 加大减少折行。配置完成后启动 Herdr 通常是一个前台命令加一个--daemon参数把运行时放到后台。此时 Herdr 会读取配置逐个拉起 Agent并为每个 Agent 创建独立的 PTY 会话。任何 Agent 异常退出Herdr 都会在日志里记录退出码和最近一段输出方便排查。4.2 任务调度与资源控制的实操建议配置只是第一步真正用好 Herdr 要从调度和资源控制上下手。先说并发数。默认情况下Herdr 会尽量把配置里的所有 Agent 都拉起来但你的机器可能并不具备同时推理多个模型的条件。我自己的做法是先看机器配置如果只有一块普通消费级 GPU同时跑两个 Agent 的推理已经比较吃力三个以上基本会互相拖累响应速度肉眼可见地下降。所以建议在配置里显式设置并发上限让 Herdr 按队列调度而不是一次性全拉起。再说日志策略。每个 Agent 的 PTY 输出都会写到独立日志文件这个文件如果不做滚动跑一次长任务可能就几百 MB。Herdr 提供了日志滚动配置可以按大小或按时间轮转。我会设置max_size: 50MB和max_files: 3保证磁盘占用可控同时保留最近几次任务的完整输出用于排查。最后是任务编排。Herdr 本身不是工作流引擎它不负责“Agent A 做完后自动通知 Agent B”这种逻辑。但它提供了 API 和事件钩子webhook可以在 Agent 状态变化时向外发送通知。我在项目里就是配合外部脚本轮询 Herdr 的状态接口一旦检测到某个 Agent 完成再触发下一个 Agent 的任务。这个“外置编排”的做法比把编排逻辑塞进 Herdr 配置里更灵活因为任务之间的依赖关系往往和项目本身强相关放外面更好维护。5. 常见问题与排查技巧实录5.1 典型问题的表现与根因分析用 Herdr 跑了两三个月的多 Agent 并行任务我积累了一份高频问题清单每次遇到相似症状都能快速定位到根因。问题表现可能根因排查思路与解法Agent 启动后立刻退出日志里只有一段空白command配置的 Agent 路径不存在或者 Agent 检测到非交互模式直接报错退出先手动在终端里跑一遍该命令确认能正常进入交互模式再检查pty.enabled是否为 true两个 Agent 同时改同一个文件代码互相覆盖工作目录未隔离多个 Agent 共享cwd给每个 Agent 配置独立的cwd必要时先 git 分支隔离再让 Agent 各自工作端口冲突导致 Agent 测试失败多个 Agent 的测试服务都监听同一默认端口在env里为每个 Agent 注入不同的PORT变量或者让 Agent 从配置文件读取端口一个 Agent 卡死CtrlC 无法停止PTY 会话里的进程没有正确响应 SIGTERM可能有子进程阻塞在配置里调成强制停止策略先发 SIGTERM 等 5 秒超时再发 SIGKILL观察日志确认子进程被清理日志文件膨胀磁盘被占满没有配置日志滚动策略开启max_size和max_files并对单次长任务的输出量做预估Agent 的输出和另一个 Agent 的日志混在一起多个 Agent 使用了同一个日志文件或输出流检查 Herdr 配置中每个 Agent 的log_dir确保独立不要在命令里强制 21这个问题清单里最常见也最隐蔽的是端口冲突。Agent 跑测试时默认监听端口来自框架约定写死在代码里的情况很多而 Agent 又不会主动去读其他 Agent 的配置所以必须靠 Herdr 注入环境变量来强制区分。我在配置里会给每个 Agent 分配一段端口区间比如codex-worker-1用 8100-8200pi-agent-runner用 8201-8300能让冲突概率降低很多。5.2 实操中的几条独家经验最后分享几个我在实际使用中摸索出来的小技巧一般文档里不会写。第一给 Agent 单独的 workspace 之后还要把源码先 clone 好而不是让 Agent 自己 clone。Agent 在 clone 大仓库时容易因为输出过多、上下文过长而“迷失方向”而且 clone 时间完全不可控。我现在都是提前在cwd目录里准备好代码让 Agent 从“修改已有代码”这个步骤开始工作效率高很多。第二注意 PTY resize 带来的输出截断。Agent 拿到一个 40x120 的终端遇到长度超过 120 字符的行会按终端宽度折行。人类在终端里看到折行没问题但 Agent 在解析自己读到的输出时折行后的内容会多出换行符可能干扰它的判断。如果发现 Agent 总是对某些输出理解偏差试试把cols调大到 200 以上让输出尽量保持单行完整。第三定期查看 Herdr 的会话日志不要只在出错时看。每个 Agent 的状态变化都会记录在独立日志里平时养成抽空扫一眼的习惯能提前发现 Agent 在循环重试、反复执行同一条命令之类的异常。这类问题早期发现就是一分钟的事拖到后期往往已经污染了代码库清理成本高得多。第四尽量用 git 分支隔离每个 Agent 的工作。Herdr 只管进程和会话不管代码版本。两个 Agent 即使是同一个需求也建议从同一个基线分支各自切分支工作最终由人来合并。这样即使 Agent 改错了也不会影响主线回滚成本很低。我自己项目里的标准流程是每个 Agent 一个分支Agent 工作完成后由我 review 并合并Herdr 负责保证 Agent 们并行跑得稳。这些经验和教训都是我一遍一遍试出来的。Herdr 这个工具本身不算复杂真正的复杂度来自多 Agent 并行工作带来的系统性问题。工具只负责把底层环境管好怎么编排任务、怎么隔离风险还是得靠使用者的工程经验。希望这篇文章能让你在走这条路的时候少踩几个我已经踩过的坑。
返回列表