☰
给AI Agent造一个专属终端:从Claude Code到多Agent并行调度的架构实践
2026/10/27 9:07:23 网站建设 项目流程

1. 从"AI 只会聊天"到"AI 能动手干活"的转折点

我最早用 Claude Code 的时候,最大的别扭感不是它不够聪明,而是它被"关在盒子里"。你让它改个文件,它得先问你路径;你让它跑个测试,它得让你把输出贴回去;你想让它同时处理前端和后端两个仓库,它只能一个窗口一个窗口地等你切过去。这种体验就像请了个很厉害的工程师,但把他锁在会议室里,所有信息都得靠你人工传递。

后来我把这个痛点拆开看,发现本质上是三个问题叠在一起:第一,AI Agent 缺少一个稳定的、可编程的终端执行层,它没法像人一样"打开终端、敲命令、看回显、判断结果";第二,单个 Agent 的上下文窗口和注意力是有限的,一个 Agent 同时管五件事,必然顾此失彼;第三,多个 Agent 之间没有共享的工作台,各自为战,互相不知道对方在干什么。

我做的这个项目,就是给 AI Agent 写一个专属终端。它不是给人类用的终端模拟器,而是给 Agent 用的"操作台"——Claude Code 可以通过它直接执行终端命令、读取结构化回显、感知命令状态,同时我还能在这个终端里并行调度多个 Agent,让它们各管一摊、互不打架。关键词里的AI Agent、Claude Code、终端、MCP、Agent这五个词,基本就是这个项目的全部骨架。

这篇文章适合三类人看:一是已经在用 Claude Code 但觉得"手脚被绑住"的开发者;二是正在搭 AI Agent 工作流、卡在"怎么让 Agent 真正操作环境"这一步的人;三是对 MCP 协议感兴趣、想搞清楚它到底解决什么问题的人。我会把架构选型、终端复用逻辑、多 Agent 调度、MCP 接入细节、以及我踩过的坑全部摊开讲,代码和配置能给的都给,尽量让你看完能直接复现。

先说结论:这个终端不是"又一个 Tabby 或者 iTerm2",它的定位是Agent 的执行后端。人类终端追求的是交互体验,Agent 终端追求的是可解析、可并发、可回放、可隔离。这四个词决定了后面所有的设计取舍。

2. 为什么现成的终端工具喂不饱 Agent

2.1 人类终端和 Agent 终端的根本差异

我一开始的想法很朴素:既然 Claude Code 能执行命令,那我直接让它调用系统 shell 不就行了?实测下来问题一大堆。人类终端的设计前提是"有个活人在看屏幕",所以它输出的是带 ANSI 转义序列的彩色文本、有光标闪烁、有交互式提示符等待输入。这些对人类友好,对 Agent 就是灾难——Agent 拿到一堆\x1b[32m之类的控制字符,根本没法稳定解析。

我拿 Tabby 终端工具做过对比测试,它的渲染层做得非常漂亮,但它的输出是面向"显示"优化的,不是面向"解析"优化的。Agent 需要的是结构化的命令结果:这条命令退出码是多少、标准输出是什么、标准错误是什么、执行耗时多久、当前工作目录在哪、有没有产生子进程。这些信息在人类终端里要么被吞掉,要么藏在渲染层里拿不到。

所以第一个核心设计决策就出来了:Agent 终端必须把"执行"和"呈现"彻底分离。执行层负责跑命令、抓状态、管生命周期;呈现层才是给人看的。Claude Code 只跟执行层打交道,拿到的永远是干净的结构化数据。

2.2 交互式命令是最大的拦路虎

真正让我头疼的是交互式命令。比如npm init会问你一堆问题,ssh会等你输密码,vim会进入全屏模式。人类终端天然支持这些,因为人能实时响应。但 Agent 执行命令是"发出去等结果"的模式,一旦命令卡在等待输入,整个流程就死锁了。

我的处理方案是给命令执行加超时和输入注入机制。每条命令执行时,终端会维护一个状态机:运行中、等待输入、已完成、超时、被中断。如果检测到命令在等待输入(通过分析输出末尾是否有提示符特征),Agent 可以选择注入预设输入,或者直接判定为"需要人工介入"并抛出事件。这个机制听起来简单,但实现时踩了不少坑,后面第 5 节会详细讲。

2.3 多 Agent 场景下终端必须支持"会话隔离"

单个 Agent 用一个终端还好说,多个 Agent 同时跑就麻烦了。如果它们共享一个 shell 会话,环境变量会互相污染,工作目录会互相干扰,一个 Agent 跑了cd /tmp,另一个 Agent 的下一条命令就在错误目录里执行了。

我试过用多个独立进程来隔离,但进程启动开销大,而且 Agent 之间想共享一些状态(比如"我已经装好了依赖")又变得很麻烦。最后我采用的是会话(Session)+ 工作区(Workspace)两级隔离:每个 Agent 绑定一个会话,会话内部有独立的环境变量、工作目录、命令历史;多个会话可以挂在同一个工作区下,共享文件系统和部分缓存。这样既隔离又协作,实测下来是最平衡的方案。

提示:会话隔离的粒度要按"任务"划分,不要按"Agent 数量"划分。一个 Agent 处理多个任务时,也应该为每个任务开独立会话,否则上下文会串。

3. 终端核心架构:从命令下发到结果回传的完整链路

3.1 整体分层设计

这个终端的架构我改了三版,最终定型为四层:

层级职责关键技术点
接入层接收 Agent 请求,暴露 MCP 接口MCP 协议、JSON-RPC
调度层会话管理、命令排队、并发控制会话池、优先级队列
执行层真正跑命令、抓状态PTY、进程组管理
状态层命令历史、结果缓存、回放结构化日志、快照

接入层用 MCP 协议对外暴露能力,这是关键。MCP 是什么?简单说它是一套让 AI 模型和外部工具对话的标准协议,模型通过它调用工具、拿结果。Claude Code 原生支持 MCP,所以我把终端能力包装成 MCP 工具后,Claude Code 不需要任何改造就能直接调用。这也是为什么关键词里 MCP 和 Claude Code 会绑在一起——MCP 是 Agent 和终端之间的"插座标准"。

调度层是我花时间最多的地方。它要解决的核心问题是:多个 Agent 同时发命令时,怎么保证不互相踩踏,又怎么保证高优先级任务先执行。我用的是会话池 + 每会话一个命令队列的设计,会话之间完全并行,会话内部串行。这样既避免了同一会话内的竞态,又保证了跨会话的吞吐。

3.2 为什么用 PTY 而不是普通管道

执行层有个关键选择:用普通管道(pipe)还是伪终端(PTY)。我一开始图省事用了 pipe,结果发现很多命令的行为会变。比如ls在 pipe 模式下不输出颜色(这倒无所谓),但有些命令会检测到"输出不是终端"而切换到非交互模式,甚至拒绝执行。更麻烦的是,pipe 模式下拿不到命令的实时输出,只能等命令结束一次性读取,这对长时间运行的命令(比如构建、测试)非常不友好。

换成 PTY 之后,命令以为自己在一个真终端里跑,行为完全正常,而且输出是流式的,可以实时抓取。代价是 PTY 会带 ANSI 转义序列,需要在状态层做一次清洗。我的做法是保留原始输出用于回放,同时生成一份清洗后的纯文本给 Agent 用。这样两边都不耽误。

3.3 命令状态机的设计

命令从下发到结束,我定义了六个状态:

  1. Pending:已入队,等待执行
  2. Running:正在执行
  3. AwaitingInput:检测到等待输入
  4. Completed:正常结束
  5. Failed:非零退出码
  6. Timeout:超时被终止

状态之间的转换不是靠猜,而是靠组合信号判断:进程是否存活、退出码、输出末尾特征、执行时长。其中AwaitingInput的判定最 tricky,我用的是"输出在最近 N 毫秒内停止增长 + 末尾匹配提示符正则"的双条件。单靠任何一个都会误判,比如一个命令在思考(CPU 密集但没输出)会被误判为等待输入,加上时间窗口后就稳多了。

# 命令状态判定的核心逻辑(简化版) def detect_state(proc, last_output_time, output_buffer): if not proc.poll() is None: return "Completed" if proc.returncode == 0 else "Failed" idle_ms = now() - last_output_time if idle_ms > IDLE_THRESHOLD and matches_prompt(output_buffer): return "AwaitingInput" if now() - proc.start_time > TIMEOUT: return "Timeout" return "Running"

这段逻辑看着简单,但IDLE_THRESHOLD和matches_prompt的调参花了我好几天。阈值太短会误判,太长会让 Agent 干等。最后我把它做成了可配置项,不同场景用不同值。

4. 多 Agent 并行调度:让 Claude Code 指挥一支"小队"

4.1 多 Agent 不是简单开多个窗口

很多人理解的多 Agent 就是"多开几个 Claude Code 窗口",这其实是最低效的做法。真正的多 Agent 调度要解决三个问题:任务怎么分、状态怎么同步、冲突怎么解。

我的设计里,有一个"主 Agent"负责拆解任务和分派,多个"工作 Agent"负责执行。主 Agent 通过终端暴露的调度接口,把任务下发给不同的工作 Agent,每个工作 Agent 绑定一个独立会话。主 Agent 不需要知道工作 Agent 具体怎么执行,只需要拿到最终结果。这其实就是经典的 orchestrator-worker 模式,关键词里的"agent 主流架构"说的就是这类。

任务分派我用的不是简单的轮询,而是基于能力标签的匹配。每个工作 Agent 注册时会声明自己擅长什么(比如"前端构建""数据库迁移""日志分析"),主 Agent 分派任务时按标签匹配。这样避免了"让一个只会跑测试的 Agent 去改配置文件"这种错配。

4.2 会话间的状态同步机制

多 Agent 并行最大的坑是状态不同步。举个例子:Agent A 装了一个依赖,Agent B 不知道,又装了一遍,浪费时间和磁盘;或者 Agent A 改了某个配置文件,Agent B 还在用旧配置跑测试,结果全是假失败。

我的解法是在工作区层面维护一个共享状态板,记录关键事件:依赖安装、文件变更、服务启动、端口占用。每个 Agent 执行命令前可以查询状态板,执行后可以更新状态板。状态板不是强一致的,而是最终一致的——允许短暂的不一致,但通过事件通知机制尽快收敛。这个取舍很重要,强一致会拖慢并发,最终一致在大多数开发场景下够用。

{ "workspace_id": "proj-alpha", "shared_state": { "dependencies_installed": ["react", "vite", "vitest"], "ports_in_use": [3000, 5173], "files_modified": ["src/App.tsx", "vite.config.ts"], "services_running": ["dev-server"] } }

Agent 拿到这个状态板,就知道"依赖已经装好了,我不用再装""3000 端口被占了,我换个端口"。这一下就省掉了大量重复劳动和冲突。

4.3 冲突检测与自动退避

即便有状态板,冲突还是会发生。最常见的是两个 Agent 同时想改同一个文件,或者同时想占用同一个端口。我的处理是乐观执行 + 冲突检测 + 自动退避:允许 Agent 先干,干的时候记录它动了哪些资源;如果检测到资源冲突,后到的 Agent 会被要求退避重试。

退避策略我用的是指数退避加随机抖动,避免多个 Agent 同时重试又撞在一起。实测下来,三到五个 Agent 并行时,冲突率能控制在很低水平,而且大部分冲突都能自动化解,不需要人工干预。

注意:自动退避要有上限,重试超过 N 次还没成功,就应该上报给主 Agent 或人类,而不是无限重试。我设的上限是 5 次。

5. MCP 接入实战:把终端能力"插"进 Claude Code

5.1 MCP 工具的定义与注册

MCP 的核心是"工具(Tool)"这个概念。我要把终端能力暴露给 Claude Code,就得把每个能力定义成一个 MCP 工具。我定义了这么几个核心工具:

  • terminal_execute:执行一条命令,返回结构化结果
  • terminal_session_create:创建一个新会话
  • terminal_session_list:列出所有活跃会话
  • terminal_input_send:向等待输入的命令发送输入
  • terminal_state_query:查询工作区共享状态

每个工具都有明确的输入 schema 和输出 schema。这里有个经验:schema 要尽量窄,不要设计成"万能工具"。我一开始想做一个terminal_do_everything工具,参数一大堆,结果 Claude Code 经常传错参数。拆成多个专用工具后,调用准确率明显提升。

{ "name": "terminal_execute", "description": "在指定会话中执行一条 shell 命令", "inputSchema": { "type": "object", "properties": { "session_id": {"type": "string"}, "command": {"type": "string"}, "timeout_ms": {"type": "integer", "default": 30000}, "cwd": {"type": "string"} }, "required": ["session_id", "command"] } }

5.2 Claude Code 侧的配置

Claude Code 接入 MCP 服务需要在配置里声明服务地址和启动方式。我这边终端是以本地服务形式跑的,Claude Code 通过标准输入输出或者本地端口跟它通信。配置好之后,Claude Code 启动时会自动拉取工具列表,然后在需要的时候调用。

这里有个容易忽略的点:工具描述(description)的质量直接决定 Claude Code 会不会正确使用它。描述要写清楚"这个工具干什么、什么时候用、参数什么意思、返回什么"。我一开始描述写得太简略,Claude Code 经常该用的时候不用,或者用错工具。把描述写详细之后,命中率大幅提升。

5.3 流式输出的处理

长时间运行的命令,输出是流式的。MCP 本身支持流式返回,但 Claude Code 对流的消费方式有讲究。我的做法是:命令执行过程中,终端持续把输出块推给 Claude Code;Claude Code 可以选择实时展示,也可以攒着等命令结束再处理。对于构建、测试这类命令,实时展示很有用,用户能看到进度;对于快速命令,攒着处理更高效。

流式输出还有个好处是可以提前终止。如果 Claude Code 从输出里发现命令已经出错了(比如看到 "Error" 关键字),它可以主动中断命令,不用等它跑完。这在跑长测试时特别省时间。

6. 那些文档里不会写的踩坑记录

6.1 环境变量污染导致的"幽灵 bug"

有一次我调试了整整一个下午,发现 Agent B 跑测试总是失败,但同样的命令我在 Agent A 的会话里手动跑就成功。排查到最后发现是环境变量污染:Agent A 在某个命令里export了一个变量,而这个变量通过某种方式泄漏到了 Agent B 的会话。

根因是我早期版本的会话隔离做得不彻底,子进程继承了父进程的环境。修复方案是每个会话启动时用干净的环境变量快照,会话内的修改只在本会话生效。这个坑的教训是:隔离要隔离到底,不能有"大部分隔离了"这种中间状态。

6.2 命令超时设置的两难

超时设太短,正常的长命令会被误杀;设太长,卡死的命令会拖垮整个调度。我最后的方案是分级超时 + 动态调整:快速命令默认 30 秒,构建类命令 5 分钟,测试类命令 10 分钟,同时允许 Agent 显式指定超时。另外加了一个"心跳检测":如果命令在持续输出,即使超过基础超时也允许继续;如果完全没输出又超时,就果断杀掉。

6.3 输出编码的坑

有些命令的输出不是 UTF-8,比如某些老工具输出 GBK,或者二进制文件被误当文本读。直接按 UTF-8 解码会抛异常或者产生乱码。我的处理是先按字节流抓取,再做编码探测,探测失败就用替换字符兜底,同时把原始字节保留下来供需要时回查。这个细节很小,但不处理的话,Agent 会拿到一堆乱码,然后基于乱码做出错误判断。

6.4 会话泄漏

早期版本我忘了在 Agent 任务结束后清理会话,结果跑了一晚上,系统里堆了几百个僵尸会话,内存直接爆了。后来加了会话生命周期管理:会话有 TTL,超时自动回收;Agent 显式结束时立即回收;系统定期扫描孤儿会话。这个坑很典型,任何资源池都要有回收机制,不能只借不还。

7. 实测效果与几个真实使用场景

7.1 场景一:全栈项目的并行开发

我拿一个前后端分离的项目做测试。主 Agent 把任务拆成"前端组件开发""后端接口开发""数据库迁移"三块,分派给三个工作 Agent。三个 Agent 各自在独立会话里干活,通过共享状态板同步"接口契约"和"数据库 schema"。实测下来,原本串行要两小时的工作,并行后四十分钟左右完成,而且因为状态同步到位,联调时几乎没有接口对不上的问题。

7.2 场景二:大规模测试的并行执行

一个项目有上千个测试用例,串行跑要很久。我让主 Agent 把测试按文件分组,分派给多个工作 Agent 并行跑,每个 Agent 负责一组。终端负责收集所有 Agent 的结果并汇总。这里的关键是结果汇总要能区分"真失败"和"环境导致的失败",否则并行跑出来的失败列表会误导人。我在状态层加了失败分类逻辑,把环境问题单独标出来。

7.3 场景三:长时间任务的监控与干预

有些任务要跑很久,比如数据迁移、大规模构建。以前我只能干等,现在 Claude Code 可以通过终端实时监控输出,发现异常就中断,或者注入输入继续。有一次一个迁移脚本卡在确认提示上,Claude Code 检测到AwaitingInput状态,自动注入了确认输入,任务顺利继续。这种"AI 自己看着办"的体验,是单靠人类终端做不到的。

8. 关于 Agent 安全边界的一点个人看法

能力越大,越要划边界。这个终端让 Agent 能执行任意命令,这本身就是个风险点。我在设计时加了几道防线:命令白名单/黑名单(危险命令如rm -rf /直接拦截)、资源配额(单会话 CPU、内存、磁盘写入上限)、操作审计(所有命令和结果留痕,可回溯)、敏感路径保护(系统目录、密钥文件禁止访问)。

这些不是可选项,是必选项。我见过太多人为了图方便把限制全关了,结果 Agent 一个误操作把重要文件删了。Agent 安全这个事,宁可麻烦一点,也不要事后后悔。关键词里"agent 安全"能成为热词,说明大家都意识到这个问题了。

9. 后续可以继续深挖的方向

这个终端目前跑得挺稳,但还有几个我想继续做的方向。一是命令的语义理解,现在 Agent 拿到的是原始输出,如果能自动把输出解析成结构化数据(比如把测试输出解析成"通过/失败/跳过"的统计),Agent 用起来会更省心。二是跨机器的会话调度,现在会话都在本机,如果能调度到多台机器上,并行能力还能再上一个台阶。三是和更多 Agent 框架的对接,MCP 是个好标准,但不同框架的接入细节还有差异,做成通用的适配层会更有价值。

如果你也在做类似的东西,我的建议是先从单 Agent 单会话跑通,再逐步加并发和隔离,不要一上来就搞复杂架构。我第一版就是贪大求全,结果调试了两周才跑通最基本的功能。先把一条命令的执行链路做扎实,再谈多 Agent 调度,这个顺序不能反。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询