Day 13 / 共 20 天 · 第 3 周 Agent 大脑

Runtime 运行时

Agent 产生了一个 Action,谁真正去执行它?答案是 Runtime(运行时)。今天讲 Action 执行的完整链路、运行时抽象、三种实现,以及 Runtime 和 Sandbox 的关系。昨天(Day 12)我们把"工具"拆开看清了它的三件套;今天顺着往下——工具被 LLM 选中、变成一个 Action 之后,是谁在哪里真正把它执行掉?答案就是 Runtime。这也为明天(Day 14)讲"大脑的智力来源 LLM"补上执行侧的最后一块拼图。

📍 第 3 周 Agent 大脑(SDK)· 你在这里
Day11 SDK概念Day12 工具体系Day13 RuntimeDay14 LLM&提示词Day15 CodeAct
L01

Runtime 是什么

代码位置:Runtime 的实现在外部包(openhands-agent-server/openhands-tools,跑在沙箱容器内)。本课讲清它的职责和分层,并对照本仓的 RUNTIME 环境变量、SandboxService(Day 09)理解。

Runtime = "执行 Action、产生 Observation"的引擎。Agent 大脑(Day 11)只负责"决定做什么"(产出 Action),真正"去做"(在系统里跑命令、读写文件、开浏览器)由 Runtime 完成。

大脑和手的分工 Agent(Day 11)是"大脑"——想"我要跑 pytest"。但大脑不会自己碰键盘,它把"跑 pytest"这个 Action 交给 Runtime(手)去真正执行。Runtime 在沙箱里敲下命令、收集输出,包成 Observation 递回大脑。Runtime 是"意图"和"真实世界"之间的执行器。
💡 本质:Runtime 就像一间"车间",而每个 Action 是一张"工单"。Agent 大脑坐在办公室里开工单:"去把 pytest 跑一遍""把这个文件第 3 行改掉"。工单本身不会自己变成结果——它被送进车间(Runtime),车间里的师傅(对应执行器)照工单操作机器,干完把成品和检验报告(Observation)送回办公室。本篇后面都在这个"办公室开工单、车间照单干活"的世界观里讲。
L02

Action 执行的一条链

Agent产出 Action Runtime识别 kind 对应执行器bash/文件/浏览器 真实执行在沙箱里 Observation包装结果 回 Agent
读法:Runtime 拿到 Action,看它的 kind(Day 02 的判别式)路由到对应执行器:ExecuteBashAction 交给终端执行器、FileEditorAction 交给文件执行器、Browser*Action 交给浏览器执行器。执行完把结果包成对应的 Observation 返回。这就是 Day 02 讲的"Action/Observation 孪生对称"在执行层的兑现——运行时能用统一方式处理"动作→观察"。
📝 第一人称请求之旅:我是一张工单 大家好,我是一个 ExecuteBashAction{command:"pytest"},刚被大脑开出来。① 我被送进车间 Runtime;② 门口的调度员看我的 kind——哦,是"执行 bash"类工单,把我派给终端执行器那台机床;③ 机床在沙箱的持久终端里敲下 pytest,等它跑完;④ 收集到 exit_code=1 和一堆失败输出;⑤ 把这些裹进一张检验报告 ExecuteBashObservation;⑥ 我"变身"成这张报告,被送回办公室交给大脑——大脑一看有测试挂了,又会开下一张修 bug 的工单。我的一生就这样完成了。
工单 Action看 kind 终端执行器 文件执行器 浏览器执行器 在沙箱执行车间干活 Observation 按 kind 派给对应机床 → 干活 → 包成检验报告回办公室
图:Runtime 按 Action.kind 路由到对应执行器,执行后包装成 Observation 返回
L03

Runtime 抽象接口

Runtime 是一个抽象——核心就一个"执行 Action"的方法(概念):

# 概念:Runtime 接口
class Runtime:
    async def run_action(self, action: Action) -> Observation:
        # 根据 action.kind 路由到具体执行器,返回对应 Observation
        ...
    # 还有连接/关闭/上传下载文件等生命周期方法
读法:只要实现 run_action(action) -> observation,就是一个合法 Runtime。不同实现(本地/Docker/远程)在"到底在哪、怎么执行"上不同,但对 Agent 大脑暴露的接口完全一样——大脑不关心 Action 是在本地进程跑还是在远程容器跑,它只管交给 Runtime。
这又是"面向接口 + 依赖注入"(Day 06 反复见):Agent 依赖 Runtime 接口,不依赖具体实现。所以同一个 Agent,配不同 Runtime 就能在不同环境执行。换 Runtime 不用改 Agent 一行代码。
L04

三种运行时实现

RUNTIME 环境变量选择(Day 04/06 见过 config.py 据它选 SandboxServiceInjector):

  • RUNTIME=local/process本地进程运行时——直接在宿主机(或本地进程)执行。快,但没隔离,开发/自研用。
  • RUNTIME=docker(默认):Docker 运行时——在隔离容器里执行。安全,普通用户用。
  • RUNTIME=remote远程运行时——在远程机器/集群的容器里执行。适合大规模/云部署。
同一个 Agent,三种"身体" 想象 Agent 是灵魂,Runtime 是它附身的"身体"。本地身体(快但脆弱)、Docker 身体(隔离安全)、远程身体(可无限扩展)。灵魂(Agent 逻辑)完全一样,换身体只改一个环境变量。这就是为什么 Day 04 说 RUNTIME=local && make run 能"用 OpenHands 开发 OpenHands"——本地身体跑起来快,适合调试。
L05

Runtime vs Sandbox:别混淆

🤔 痛点:Sandbox 和 Runtime 听起来都是"跑代码的地方",到底啥区别?很多人到这里会卡住——两个词都和"执行""容器"沾边,很容易糊成一团。

这两个概念容易混,理清楚:

  • Sandbox(沙箱,Day 09):app_server 视角的"执行环境资源"——一个 Docker 容器,有生命周期(供给/暂停/删除)。管家管理它。
  • Runtime(运行时,今天):agent-server 视角的"执行 Action 的引擎"——跑在沙箱容器内部,负责真正 run_action。
关系:Sandbox 是"工作间"(房子),Runtime 是"工作间里干活的手"(住在房子里的工人的手)。app_server 负责供给沙箱这个房子(Day 09),沙箱里的 agent-server 用 Runtime 在房子里执行 Action一句话:Sandbox 是资源(房子),Runtime 是执行器(手);房子由管家供给,手在房子里干活。两者都可以是 local/docker/remote,且通常一致(Docker 沙箱里跑执行逻辑)。
⚠️ 小白常误以为:Runtime 和 Sandbox 是同一个东西的两个名字。其实:Sandbox 是"房子/车间厂房"这个资源,由 app_server 这个管家去供给和回收(Day 09);Runtime 是"车间里干活的那双手"这个执行器,住在房子里、由 agent-server 驱动。一个是场地,一个是场地里的操作者——层次不同,别划等号。
L06

Workspace 工作区

Runtime 执行 Action 时,文件操作发生在一个 Workspace(工作区)——沙箱里的一个目录,你的项目就 clone 在这里(Day 07 的 working_dir/get_project_dir)。本仓用 AsyncRemoteWorkspace 抽象来远程操作沙箱里的工作区。

Workspace 就是 Agent 的"工位桌面" Agent 干活总要有个地方放文件——那就是 Workspace。你选的 repo clone 到这里,setup.sh 在这里跑,Agent 的所有文件编辑都在这里发生。删除会话时"归档 workspace"(Day 07)就是保存这个目录的成果。project_dir = {working_dir}/{repo_name}(选了 repo 时),所有 .openhands/ 特性(setup 脚本、git hooks、skills、PLAN.md)都以它为基准(app_conversation_service_base.py:58)。
L07

为什么这样分层

  • Agent 与执行解耦:大脑只产 Action,不关心怎么执行。换执行环境不影响 Agent 逻辑。
  • 安全:Runtime 在沙箱里跑,AI 产生的命令永远在隔离环境执行,碰不到宿主机。
  • 可测试:测 Agent 逻辑时可以用假 Runtime(返回预设 Observation),不真跑命令——这和 eino 教程里"用 fakeModel 测编排"一个道理。
  • 可扩展:加一种新执行环境(比如 K8s、WebAssembly),只需实现 Runtime 接口。
回味整个 Agent 大脑的分层:Agent(决策,Day 11)→ Tools(能力定义,Day 12)→ Runtime(执行,Day 13)→ LLM(智力,Day 14)。每一层职责单一、面向接口、可替换。这套"决策/能力/执行/智力"分离的架构,是所有严肃 Agent 框架的共同骨架。
🎵 记忆口诀"脑决策、具定义、时执行、力供智"——脑(Agent)负责决策、具(Tools)定义能力、时(Runtime)负责执行、力(LLM)供给智力。或者更白话一句:大脑开工单,工具列菜单,车间照单干,模型给灵感。四层各管一段,谁也不越界。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • Runtime 是什么?和 Agent 大脑怎么分工?
  • Action 执行的一条链是怎样的?
  • 三种 Runtime 实现由什么选择?"同一灵魂三种身体"怎么理解?
  • Runtime 和 Sandbox 的区别与关系?(手 vs 房子)
  • Workspace 是什么?为什么删会话要归档它?

✋ 动手

# RUNTIME 如何选择运行时/沙箱实现(本仓)
grep -n "getenv('RUNTIME')" openhands/app_server/config.py
# Workspace / working_dir / project_dir(本仓)
grep -n 'AsyncRemoteWorkspace\|working_dir\|def get_project_dir' \
  openhands/app_server/app_conversation/*.py | head
# 外部执行包
grep 'openhands-agent-server\|openhands-tools' pyproject.toml
明天预告 · Day 14LLM 抽象与提示词——OpenHands 怎么用 litellm 统一对接上百种大模型、系统提示怎么组织、流式为什么强制开启、用量与成本怎么统计。
← Day 12 工具 Day 14 · LLM 抽象 →