ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenHands PTY沙盒:为AI Agent装上可执行命令的物理手脚

OpenHands PTY沙盒:为AI Agent装上可执行命令的物理手脚 1. 为什么 Agent 需要一副“物理手脚”很多人把 Agent 的开发理解成“套一个 Prompt、接一个大模型 API、能对话就算完事”。但真正做过 Agent 项目的人应该都有同感对话能力只是 Agent 的“大脑皮层”真正让 Agent 从“聊天机器人”升级为“能办事的智能体”的是它能不能在真实环境里执行命令、读写文件、操作软件。这一步迈不过去Agent 永远只是个高级的问答玩具。OpenHands曾经叫 OpenDevin这套开源框架核心思路就是把“执行层”认真做扎实。它给 Agent 开了一个PTY 沙盒让大模型不仅“想”还能“做”——在隔离出来的 Linux 环境里跑命令、跑脚本、改代码然后根据执行结果决定下一步动作。换句话说这一步是给 Agent 装上“手和脚”的关键工程。这套机制适合谁去研究一个是正在做 Agent 框架设计的开发者一个是想理解“AI 如何操作真实系统”的进阶爱好者。我建议至少要有基础的 Linux 命令和 Python 异步编程经验不然读下来会有一点吃力。不过我会尽量把原理讲透把坑都提前标注出来跟着走一遍下来收获会非常明显。从架构上看PTY 沙盒解决的是一个非常核心的问题Agent 与大模型之间对话只是“思考”Agent 与操作系统之间交互才是“行动”。思考需要的是语言模型的能力行动需要的是可控、可观测、可回滚的“物理层”。OpenHands 的 PTY 沙盒就是这一层的关键实现。2. 先搞清楚 PTY 到底是什么东西PTY 的全称是 Pseudo-Terminal翻译过来叫伪终端。如果你以前只用 Windows 的 CMD 或者 PowerShell这个概念可能有点陌生但在 Linux 和 macOS 上一切终端窗口的背后都是 PTY 在起作用。我尽量用大白话讲一下它的本质。你在电脑上打开一个终端窗口敲下ls然后回车这个过程中其实发生了两件事终端窗口把ls这个输入交给 shell 进程去执行shell 执行完了把结果输出回终端窗口显示。这个“终端窗口”和“shell 进程”之间的桥梁就是 PTY。PTY 分成两端主端master和从端slave。从端接的是 shell 进程主端接的是用户终端程序或者像 Agent 这样的调用方。从端看起来就像一台真实的终端设备shell 所有的输入输出都走这个“假终端”而主端是控制方可以写入输入、读取输出甚至发送终端控制信号。为什么要绕这么一圈直接subprocess.run(ls)不好吗这里面有几个很关键的差异你得理解透第一很多交互式程序比如 Python 交互式解释器、vim、top 这类工具会检测自己是不是连在一个终端上。如果检测不到终端它们会拒绝以交互模式启动或者行为完全不一样。用subprocess直接跑拿不到交互式效果。第二PTY 能处理终端控制序列比如 ANSI 颜色、光标移动、清屏操作。这些对于 Agent 解读程序输出特别重要——你总不希望 Agent 面对一堆乱七八糟的\x1b[2J转义序列直接蒙圈吧。第三PTY 天然就是一个字节流通道读写非常接近真实终端行为。你可以把 Agent 对终端的操作想象成一个人在一台实体机器前面敲键盘、看屏幕——只不过这个“人”是大模型这台“机器”是 Docker 容器。提示PTY 不是 OpenHands 发明的技术早在几十年前的 UNIX 时代就有了。OpenHands 的贡献在于把 PTY 封装成了 Agent 可以理解、可以控制、可以观测的“工具层”。3. OpenHands 沙盒的设计思路拆解OpenHands 的沙盒架构一句话总结就是用 Docker 做隔离用 PTY 做交互通道用事件流Event Stream做 Agent 大脑与执行环境之间的通信协议。这三个部分组合起来才构成了一个真正“可操作”的 Agent。3.1 为什么沙盒选择 Docker 而不是虚拟机安全隔离这件事情Agent 比人更“危险”——因为大模型的执行路径不可预测你根本不知道它会因为一个模糊指令去执行什么命令。给它一台虚拟机太重了起一个环境要好几秒甚至更久。直接跑在宿主机上风险太大万一模型“抽风”执行了rm -rf /哭都来不及。Docker 的优势很明显轻量、快速、隔离性好、环境可重复构建。OpenHands 默认的做法是运行一个 Ubuntu 容器里面预先装好 Python、Node.js、Git 等常用开发工具然后通过 Docker API 来管理容器的生命周期。每次任务结束整个容器直接销毁任何脏数据都不留在宿主机上。我个人在实际测试中的体验是容器从启动到可用大概在 1 到 2 秒之间。这个速度对 Agent 的“操作型任务”至关重要——如果一个命令要等十几秒才能执行Agent 的推理链路会被拖得很长体验非常差。3.2 沙盒、事件流与 Agent 的三角关系OpenHands 的内部结构里Event Stream是一个全局总线所有组件之间的通信都通过事件来进行。沙盒执行了一条命令产出了一个输出事件Agent 看到一个输出事件决定下一步要发什么指令又产出一个行动事件。这就像一个异步的消息队列把 Agent 的“决策”和沙盒的“执行”完全解耦。这个设计最大的好处是可追踪、可回放、可恢复。Agent 在思考的过程中整个执行轨迹都会被记录下来。如果 Agent 中途崩了或者你想复盘它为什么做出某个决定可以把事件流重新播放一遍精确到每一步。从工程实践的角度看事件流的设计比你直接“大模型调函数、函数调用 shell”要稳健得多。因为后者的调用链是同步的、脆弱的前者天然支持并发、重试、暂停、恢复。3.3 PTY 在 OpenHands 中的具体位置在 OpenHands 的代码中PTY 的执行由sandbox模块管理核心类是PtyProcess。它负责在 Docker 容器里开启一个 PTY 会话并暴露一个类似文件操作的接口给上层调用。上层只需要做三件事写命令、读输出、关闭连接。写到这我想起一个对比——你去用市面上很多“AI 自动操作电脑”的工具会发现它们的实现非常粗暴要么用截图识别然后模拟鼠标点击要么直接调用一个 shell 命令拿结果。OpenHands 的做法更底层、更通用它不去猜屏幕上的内容而是直接操作终端这个最通用的“数字交互界面”。这就像一个是隔着玻璃操作机器一个是直接把手伸进控制台——效率和可控性完全不是一个级别。4. 核心逻辑拆解一个最小可用的 PTY Agent 沙盒光说理论容易飘我直接带着你从零写一个最小可用的 PTY Agent 沙盒。这里我会用 Python 作为实现语言因为 OpenHands 本身就是 Python 写的生态比较成熟。4.1 技术选型选择哪个 PTY 库Python 里和 PTY 相关的库其实有好几个我自己踩完坑之后的建议是库名特点推荐度pty标准库最底层控制粒度最细但处理异步很痛苦不推荐直接使用ptyprocess基于标准库封装提供超时控制和 expect 模式简单场景可用asyncio-pty支持 async/await适合 Agent 这种异步密集型任务推荐node-ptyNode.js 生态配合 Electron/前端很顺手前端方案可考虑我最终选择的是asyncio-pty因为它能无缝集成到asyncio事件循环里。Agent 的运行本质就是一个异步过程模型在等响应、命令在跑、输出在流动这些都不能用阻塞式调用来处理。4.2 初始化一个 PTY 进程先用 Python 代码初始化一个 PTY 会话。假设我们已经有一个正在运行的 Docker 容器容器 ID 存在container_id变量里import asyncio import asyncio_pty async def create_pty_session(container_id: str): 在指定容器内创建 PTY 会话 # 关键点在容器内执行 /bin/bash并且分配一个 PTY # -i 表示交互模式-t 表示分配终端 cmd [docker, exec, -i, -t, container_id, /bin/bash] # 创建 PTY 进程 pty_process await asyncio_pty.open(cmd, cols120, # 终端宽度 rows40, # 终端高度 cwd/workspace) # 工作目录 return pty_process这里有几个细节要特别注意cols和rows是终端的尺寸。为什么要设置这个因为很多程序会根据终端宽度自动换行或格式化输出比如ls -l的列数计算。如果你不设置默认可能是 0有些程序就会出 bug。cwd设成/workspace是为了让 Agent 一进去就在工作目录里不用执行cd命令。用docker exec -it而不是docker exec是因为-t才会分配 PTY没有-t的话交互程序和颜色输出都会失效。4.3 命令写入与输出读取有了 PTY 进程之后写命令和读输出就非常简单了async def run_command(pty_proc, command: str, timeout: float 30.0): 向 PTY 写入命令并读取输出 # 在命令后面加换行符通知 shell 执行 pty_proc.write((command \n).encode()) # 读取输出这里用了 asyncio 的超时控制 output bytearray() try: while True: chunk await asyncio.wait_for(pty_proc.read(4096), timeouttimeout) if not chunk: break output.extend(chunk) # 这里可以根据输出内容判断命令是否执行完成 # 比如检测到 shell 提示符如 $ 或 # except asyncio.TimeoutError: print(f命令 {command} 执行超时已强制停止) return output.decode(utf-8, errorsreplace)等等上面的代码有一个很隐蔽的问题我得专门拿出来说。直接读输出到超时的做法在实际使用中是有问题的。因为对交互式 shell 来说read()只要终端里有数据就会立即返回不会傻等着你命令执行完。比如你执行ls输出很短几毫秒就返回了但如果你执行sleep 5 echo donesleep期间终端里没有输出read()就会一直阻塞直到echo done输出了才返回。所以你不能单纯靠“读不到输出了”来判断命令跑完了。实践中一般有三种做法在命令后面加结束标记比如echo __CMD_DONE__然后读到这个标记就认为命令执行结束。检测提示符读取到$或#开头就认为 shell 回到了空闲状态。固定等待 超时用一个较短的时间片轮询输出没有新数据了就认为结束这个方案不推荐不够可靠。OpenHands 内部是结合了第一和第二种方案还会解析终端控制序列来过滤掉转义字符让输出的可读性更强。我们做最小实现的时候用第一种方案最可靠async def run_command_with_marker(pty_proc, command: str): 用结束标记来判断命令执行完成 marker __CMD_DONE_7f3a9__ # 执行命令并在末尾输出标记 pty_proc.write(f{command}; echo {marker}\n.encode()) output while True: chunk await pty_proc.read(4096) if not chunk: break output chunk.decode(utf-8, errorsreplace) if marker in output: # 已经把标记包含进去了可以裁剪掉 output output.split(marker)[0] break return output这个方法简单粗暴但非常管用。唯一要注意的是如果你执行的命令本身也嵌套执行命令里面的命令也会继承这个 shell 会话所以标记会在任何输出之后出现不会漏掉。4.4 完整的最小实现把上面的逻辑串起来一个最小可用的 PTY Agent 沙盒差不多长这样import asyncio import asyncio_pty import uuid class PTYAgentSandbox: def __init__(self, container_id: str): self.container_id container_id self.pty_proc None self.session_id str(uuid.uuid4()) async def start(self): 启动 PTY 会话 cmd [docker, exec, -i, -t, self.container_id, /bin/bash] self.pty_proc await asyncio_pty.open(cmd, cols120, rows40) # 等待 shell 完全就绪 await asyncio.sleep(0.5) # 清掉 shell 初始化输出 await self._clear_output() async def _clear_output(self): 清空 shell 启动时的 banner 输出 try: await asyncio.wait_for(self.pty_proc.read(4096), timeout0.2) except asyncio.TimeoutError: pass async def execute(self, command: str, timeout: float 60.0): 执行命令并返回输出 marker f__CMD_DONE_{self.session_id[:8]}__ full_cmd f{command}; echo {marker}\n self.pty_proc.write(full_cmd.encode()) output try: while True: chunk await asyncio.wait_for( self.pty_proc.read(4096), timeouttimeout ) if not chunk: break output chunk.decode(utf-8, errorsreplace) if marker in output: output output.split(marker)[0] break except asyncio.TimeoutError: output \n[ERROR] Command execution timeout return output.strip() async def close(self): 关闭 PTY 会话 if self.pty_proc: self.pty_proc.close() await self.pty_proc.wait()这个类已经具备了最基本的能力启动会话、执行命令、读取输出、超时控制、清理会话。你在实际项目中完全可以拿它作为起点继续扩展。注意上面的代码为了演示做了大量简化生产环境还需要处理信号中断、终端大小调整、并发写锁等问题。但核心的执行链路就是这样。5. 实操过程把 PTY 沙盒跑起来光有代码还不能跑你还需要一个 Docker 容器作为沙盒的“底盘”。我把自己实操的完整流程写在下面每一步都是验证过的。5.1 准备基础容器镜像我用的是 OpenHands 官方的运行时镜像不过你也可以自己做一个轻量版。先拉取镜像docker pull ghcr.io/all-hands-ai/runtime:0.9.0如果你想用更轻量的方案也可以直接用 Python 官方镜像自己装工具docker pull python:3.11-slim docker run -d --name agent-sandbox python:3.11-slim tail -f /dev/null用tail -f /dev/null保持容器在前台运行这样容器不会因为没有任何进程而退出。这是 Docker 实战里常用的小技巧适合需要一个“闲置”容器做实验的场景。5.2 在容器内安装必要工具进入容器安装一些常用工具docker exec -it agent-sandbox bash apt-get update apt-get install -y git curl wget vim build-essential pip install --upgrade pip exit这一步不是必选的但如果你希望 Agent 能完成“写代码 - 测试 - git 提交”这类完整任务这些工具早晚要装。OpenHands 的官方镜像里已经预装好了这几百 MB 的工具链直接用会更省事。5.3 跑通最小沙盒把上面的PTYAgentSandbox类保存成sandbox.py然后写一个简单的测试脚本import asyncio from sandbox import PTYAgentSandbox async def main(): sandbox PTYAgentSandbox(agent-sandbox) await sandbox.start() # 测试基本命令 output await sandbox.execute(pwd) print(fpwd 输出: {output}) output await sandbox.execute(python --version) print(fPython 版本: {output}) # 测试稍微复杂一点的操作 output await sandbox.execute(echo hello agent /tmp/test.txt cat /tmp/test.txt) print(f文件操作输出: {output}) # 测试交互式程序 output await sandbox.execute(python3 -c \print(from python)\) print(fPython 执行输出: {output}) await sandbox.close() asyncio.run(main())跑一下看看输出python test_sandbox.py如果一切正常你会看到 pwd 输出的是/Python 版本显示的是容器里的版本号所有命令都能正常执行。到这里一个最小可用的 PTY Agent 沙盒就已经跑通了。5.4 接入大模型做简单的任务闭环跑通了命令执行下一步就是把大模型接进来形成一个“思考-行动-观察”的循环。这一步是整个 Agent 开发的核心模式我给你写一个最简单的例子import asyncio from openai import AsyncOpenAI from sandbox import PTYAgentSandbox client AsyncOpenAI() # 假设已经设置了 API Key sandbox PTYAgentSandbox(agent-sandbox) SYSTEM_PROMPT 你是一个运行在 Linux 沙盒中的 AI 助手。 你有能力执行 shell 命令来完成任务。每次只输出一条命令不要解释。 当任务完成时输出 __TASK_COMPLETE__。 async def agent_loop(task: str, max_steps: int 10): await sandbox.start() history [] for step in range(max_steps): # 让大模型决定下一步做什么 messages [ {role: system, content: SYSTEM_PROMPT}, *history, {role: user, content: f任务: {task}} ] resp await client.chat.completions.create( modelgpt-4o, messagesmessages, temperature0 ) action resp.choices[0].message.content.strip() if __TASK_COMPLETE__ in action: print(f任务完成共执行了 {step} 步) break # 执行命令并观察结果 print(f执行: {action}) result await sandbox.execute(action) print(f结果: {result[:200]}) # 把执行结果作为观察追加到对话历史 history.append({role: assistant, content: action}) history.append({role: user, content: f观察结果:\n{result}}) await sandbox.close() asyncio.run(agent_loop(查看 /etc/os-release 的内容))这个例子非常朴素但已经完整展示了 Agent 的核心闭环。大模型基于你的任务描述一步步决定要执行什么命令然后把命令执行的结果反馈给它它再基于观察结果做下一步决策。这就是 ReActReasoning Acting模式在工程上的落地。6. 套接字层与 OpenHands 的容器化实现很多人在读 OpenHands 源码时会注意到一个比较绕的点OpenHands 并不是直接从主进程去连容器的 PTY而是通过一个在容器内运行的套接字服务来中转命令读写。这个设计初看有点多余但仔细想会发现非常聪明。直接通过 Docker API 或docker exec去操作容器每一次交互都要经过 Docker 守护进程性能和灵活性都不够好。而且容器内的进程如果直接和主进程通信网络层面也不好隔离。OpenHands 的做法是沙盒容器内跑一个runtime服务它监听一个 Unix 套接字或者 TCP 端口。主进程把要执行的命令封装成请求发到套接字上容器内的runtime服务收到请求后在本地开一个 PTY 会话去执行再把输出结果通过套接字返回。好处有三个性能更好请求不需要每次经过 Docker daemon 转发容器内直接处理路径短、延迟低。部署更灵活容器内的runtime服务可以独立升级、独立扩展不依赖宿主机的 Docker API 版本。安全边界更清晰宿主机侧只知道“有一个套接字端口”不暴露 Docker 控制能力给任何内部组件。从这个角度看OpenHands 的沙盒设计其实分了两层外层是 Docker 容器的隔离内层是 runtime 进程对 PTY 的二次封装。两个层面各司其职组合起来才形成完整的执行环境。7. 常见问题与排查技巧实录这个部分我重点聊聊实际操作中容易踩的坑。每一个我都亲自遇到过并确认了解决方案。7.1 Agent 执行命令后返回空输出现象命令明明执行了但返回的输出是空字符串。原因大多数情况下是因为 shell 的默认提示符输出被当成普通输出处理了或者命令的输出内容太短被_clear_output()方法误清了。排查思路先手动在终端里执行同样的命令看输出到底是什么。然后检查你的 PTY 会话是否真的连上了 shell可以在初始化后直接读一下有没有 banner 输出。解决方法把_clear_output()的等待时间调短一点或者干脆不用这个逻辑改用在每条命令前加一个随机标记。另一个办法是执行命令时加-l参数让 bash 以登录 shell 方式启动这样环境变量更完整不容易出现输出异常。7.2 命令一直阻塞触发超时现象Agent 执行sleep 100这类长时间命令整个流程卡住最后只能超时退出。原因这是预期的行为但问题在于你没有给 Agent 一个“中断”的手段。人眼看情况不对可以按CtrlCAgent 也需要这个能力。解决方法在execute方法里增加一个interrupt方法通过向 PTY 写入\x03即CtrlC的 ASCII 码来中断当前命令async def interrupt(self): 向当前进程发送 CtrlC 中断信号 self.pty_proc.write(b\x03) await asyncio.sleep(0.1)然后在超时逻辑里先尝试发CtrlC看命令是否停止如果还不停再考虑强制关闭会话。7.3 容器内无法启动交互式程序现象在沙盒里执行vim或htop报错说“TERM environment variable not set”之类的。原因这是环境变量的问题。虽然分配了 PTY但容器的基础镜像可能没有设置TERM环境变量。交互式程序需要通过这个变量来确定终端类型。解决方法在命令前手动设置环境变量或者启动容器时配置好docker exec -i -t -e TERMxterm-256color agent-sandbox bash也可以在初始化 PTY 时写入export TERMxterm-256color再开始会话。7.4 输出里混入大量 ANSI 转义序列现象输出里面有大量\x1b[01;32m这样的乱码。原因shell 默认会给文件类型用颜色区分比如目录是蓝色、可执行文件是绿色这些颜色信息通过 ANSI 转义序列传递。解决方法让 shell 不带颜色输出。最直接的方法是在启动命令时加--noprofile --norc不加载使用颜色的配置文件另一种是手动过滤 ANSI 转义序列。分享一个我常用的正则import re ANSI_ESCAPE_PATTERN re.compile(r\x1b\[[0-9;]*[a-zA-Z]) def strip_ansi(text: str) - str: return ANSI_ESCAPE_PATTERN.sub(, text)在生产项目中我建议保留原始输出和清洗后的输出两个字段原始数据用于调试清洗后的数据用于给大模型观察。因为有些场景下ANSI 序列里其实藏着语义信息比如光标位置直接删掉可能会影响对程序状态的判断。7.5 Agent 连续执行命令时输出错乱现象Agent 快速连续执行两条命令上一条命令的输出和下一条命令的输出混在一起。原因这是典型的竞态条件。PTY 本质是一个共享的字节流两条命令的写入和读取如果并发了就分不清哪段输出属于哪条命令。解决方法在沙盒类中加入一个asyncio.Lock保证同一时间只有一个命令在执行import asyncio class PTYAgentSandbox: def __init__(self, container_id: str): self.container_id container_id self.pty_proc None self._lock asyncio.Lock() async def execute(self, command: str): async with self._lock: # 执行命令的逻辑 pass用了锁之后Agent 的命令都是串行执行的天然避免了输出错乱。这个方案简单、可靠性能上也不会有什么损失因为绝大多数 Agent 任务的瓶颈根本不在命令执行而在大模型的推理延迟。7.6 Docker 容器启动后无法删除现象测试过程中容器卡住了docker rm -f都删不掉。原因通常是因为有 PTY 会话仍然连接到容器Docker 的进程管理认为容器还在“忙碌”状态。解决方法先清掉宿主机上持有 PTY 连接的进程再删容器# 找出连接到容器 ID 的进程 ps aux | grep container_id # 杀掉对应进程 kill -9 pid # 强制删除容器 docker rm -f container_id实战里最稳妥的方式是在 Python 代码里给close()方法加上finally块确保异常时也能执行清理逻辑不要依赖手动删容器。8. 围绕 PTY 沙盒的几个进阶思考写到这里我再说一说自己在实际项目中围绕 PTY 沙盒积累的几个经验供你参考。8.1 Agent 连接终端这件事比想象中更有价值很多人容易低估“交互式终端”这个能力。其实大多数实用工具和服务都提供了交互式的管理界面redis-cli、psql、pythonREPL。如果你的 Agent 只能非交互地执行一次性命令很多工具的高级能力就用不上。有了 PTYAgent 可以和这些程序持续对话真正做到“人怎么操作Agent 就能怎么操作”。8.2 不要忽视终端尺寸对输出的影响我可以负责任地告诉你终端宽度真的会影响程序的行为。ls -l在不同宽度下的换行方式不一样一些 TUI 程序在窗口过小时会直接拒绝启动。OpenHands 默认用 120 列实践下来比较合理。但如果你的 Agent 习惯生成很长的代码行可以考虑用 160 列减少不必要的换行符出现在输出里。8.3 沙盒里要有“痕迹管理”建议你在沙盒的~/.bash_history、/workspace文件变动日志上都做一点留痕。这样 Agent 出错时你可以快速回溯它到底做了哪些操作。就像看监控录像一样能把现场还原出来排查问题的效率能提升一个档次。8.4 超时策略要分层一个很隐蔽的问题你在 Python 层设了 60 秒超时但如果容器内执行的是apt-get update这种长时间网络操作60 秒可能不够可如果 Agent 只是在跑一个快速测试60 秒又太长等半天才发现命令出问题了。我的经验是按命令类型差异设置超时。文件操作类命令 10 秒代码执行类 30 秒包管理/网络类 120 秒这样既不会因为超时太短误伤长任务也不会因为超时太长拖慢 Agent 的节奏。8.5 会话复用比每次新建更高效如果你的 Agent 需要多次和同一个沙盒交互比如先装依赖、再跑测试、后看日志建议在整个任务期间复用一个 PTY 会话而不是每个命令都重新创建一个。会话复用的好处是工作目录、环境变量、shell 状态都是连续的就像一个人在同一个终端窗口里连续操作体验更自然、性能也更好。9. 最后一小块蛋糕让沙盒“返回状态码”我的 PTYAgentSandbox 目前只返回了标准输出和标准错误混在一起的内容但没有返回命令的退出状态码。这在很多场景下很致命——Agent 需要知道命令是成功还是失败。获取退出码的办法是借助 shell 特性在命令执行后用特殊变量$?拿到上一条命令的退出码async def execute_with_status(self, command: str): marker f__DONE_{self.session_id[:8]}__ full_cmd f{command}; echo {marker}:$?\n ...然后在解析输出时把marker后面的内容拆分出来if marker in output: output, status output.split(marker) status_code status.strip().split(:)[1] return output.strip(), int(status_code)这样每条命令的执行结果就变成了(输出内容, 退出码)二元组。给大模型观察时把退出码作为一项重要信息传进去模型就能判断这个操作是否真的成功了决策会更准确。这一步看似是个小细节但对 Agent 的实际效果影响很大。10. 下一步可以做什么你如果能自己动手把上面的代码跑通说明你已经摸到了“Agent 物理手脚”的门道了。接下来几个方向都值得深入把 PTY 输出中的ANSI 转义序列完整解析出来做成结构化的终端事件流让 Agent 更准确地理解终端状态。给沙盒增加文件快照和回滚能力每次执行任务前打个快照Agent 跑挂了还能恢复到之前的状态。做一个简单的Agent 监控面板实时显示 Agent 正在执行的命令、输出内容、耗时等指标调试体验会好很多。让沙盒支持多会话并行一次跑多个 Agent 实例做不同子任务最后汇总结果。这已经触及多智能体协作的范畴了后续可以单独写一篇。从 0 到 1 搭出 PTY 沙盒其实只是给 Agent 装上“手”的第一步。后面还有环境感知、任务拆解、长时记忆很多硬骨头要啃。但有了这一层的底子后面每一步都走得稳。
返回列表