Day 04 / 共 20 天 · 第 1 周 核心概念
运行一个 OpenHands
前三天(Day 02/03)都在讲"事件/动作"这些概念,今天把它们落地——让 OpenHands 真的跑起来。讲三种启动方式,并读 config.template.toml 的关键配置:你会发现前几天学的"动作全家福、安全评估",其实都由这里的配置旋钮开关;把配置认全了,明天(Day 05)的完整旅程就能对号入座。
📍 第 1 周 核心概念 · 你在这里
Day1 全景→
Day2 Action/Obs→
Day3 事件流→
Day4 运行→
Day5 完整旅程
L01
三种运行方式
OpenHands(Agent Canvas 形态)有三种跑法,从易到难:
| 方式 | 命令 | 适合谁 |
|---|---|---|
| npm 直接跑(无沙箱) | npm i -g @openhands/agent-canvas && agent-canvas | 快速体验(AI 有完整文件权限,有风险警告) |
| Docker(推荐) | docker run ... ghcr.io/openhands/agent-canvas | 普通用户,带沙箱隔离 |
| 从源码 | make build && make run | 开发者、想读/改源码的你 |
先搞清楚"两个进程"
回忆 Day 01:OpenHands V1 是双进程——① app_server(本仓,编排/网关,第2周主角);② agent-server(跑在沙箱容器里,真正执行 Agent 循环)。你启动时,app_server 先起来,等你下任务时它再去拉起一个 sandbox 容器(里面装着 agent-server)。理解这个"一个管家 + 按需开工作间"的模型,后面读代码就不会晕。
L02
Docker 方式(推荐,README 原文)
# README.md:88-106
export PROJECTS_PATH="$HOME/projects" # 你想让 AI 操作的项目目录
docker run -it --rm -p 8000:8000 \
-v "$HOME/.openhands:/home/openhands/.openhands" \ # 持久化配置
-v "${PROJECTS_PATH}:/projects" \ # 挂载你的项目进去
ghcr.io/openhands/agent-canvas:1.0.0-rc.11
# 然后浏览器打开 http://localhost:8000
读法:
-p 8000:8000 把界面端口映射出来;两个 -v 挂载让"配置"和"你的项目"在容器内外共享。打开 8000 端口就是 Web 界面,在 Settings 里配大模型和 API Key(V1 在 UI 里配,不用手写配置文件),然后就能下任务了。关于大模型 API Key:OpenHands 自己不含大模型,你得"自带模型"(bring your own model)——填你的 OpenAI/Anthropic/等 API Key。它用
litellm(pyproject.toml:55)统一对接上百种模型,所以几乎什么模型都能接(Day 14 讲 LLM 抽象)。L03
从源码 make run(开发者路线)
# Development.md / AGENTS.md
make build # 一键构建前后端
make run # 同时跑前端(3001)和后端(3000)
# 或分开:
make start-backend # 后端,端口 3000(等价 uvicorn openhands.app_server.app:app)
make start-frontend # 前端,端口 3001
# 用 OpenHands 开发 OpenHands 自己(本地 runtime、不装 docker):
export INSTALL_DOCKER=0
export RUNTIME=local
make build && make run
读法:后端真正的入口是
openhands.app_server.app:app(一个 FastAPI 应用)。openhands/server/ 目录现在只是"向后兼容的转发壳"(server/app.py 只 re-export app_server.app),真正实现全在 app_server——第 2 周我们就读它。RUNTIME 环境变量很关键:它决定沙箱用什么实现——docker(默认,Docker 容器)、local/process(本地进程,开发用)、remote(远程)。这一个环境变量就切换了整个沙箱后端——第 2 周 Day 09 会看到 config.py 里正是靠它选择不同的 SandboxServiceInjector。L04
配置文件总览 config.template.toml
🤔 对话:配置文件到底管什么?
👶 小白:一个 Agent 不就是"连上大模型然后干活"吗,为什么还要一大堆配置?
👨🏫 老师:因为"能连大模型"只是第一步。它能不能跑命令、能不能上网、在什么环境里跑、最多花多少钱、危险动作要不要先问你——这些都得有人拍板。
👶 小白:那我不写配置行不行?
👨🏫 老师:行,模板里每项都有默认值。
👨🏫 老师:因为"能连大模型"只是第一步。它能不能跑命令、能不能上网、在什么环境里跑、最多花多少钱、危险动作要不要先问你——这些都得有人拍板。
config.toml 就是那张"拍板清单"。👶 小白:那我不写配置行不行?
👨🏫 老师:行,模板里每项都有默认值。
config.toml 只是把你想改的项覆盖掉默认值而已。💡 本质:配置合并 = 公司报销制度config 就像生活中的公司报销制度:先有一套"全公司默认规则"(模板默认值),部门可以覆盖几条(
[agent] 等分段),个人还能特批(你在 config.toml 里改的项)。最终生效的是"层层叠加后的结果"。所以读配置就是读"这家公司到底给这个 Agent 批了哪些权限、设了哪些上限"。headless/CLI 模式用 config.toml(从 config.template.toml 拷贝改)。它是 TOML 格式,分成若干 [段]:
| 配置段 | 管什么 |
|---|---|
[core] | 运行时选择、默认 Agent、最大步数/预算、并发数 |
[agent] | Agent 能用哪些工具(浏览/编辑/命令/思考…开关) |
[sandbox] | 沙箱镜像、超时、挂载卷、GPU |
[security] | confirmation mode、安全分析器 |
[condenser] | 历史压缩策略(Day 03 提过) |
[mcp]/[kubernetes] | MCP 外部工具接入 / K8s 部署 |
配置文件为什么值得单独学?
因为读配置文件是理解一个系统能力边界最快的方式。看
[agent] 段就知道 Agent 能发哪些动作;看 [sandbox] 就知道它在什么环境跑;看 [security] 就知道有哪些安全闸。配置项 = 系统旋钮,把旋钮认全了,系统的骨架也就清楚了。L05
[core]:核心配置
[core]
runtime = "docker" # 沙箱后端:docker/local/remote
default_agent = "CodeActAgent" # 默认用哪个 Agent(Day 15 讲 CodeAct)
max_iterations = 500 # 一个任务最多循环多少步(防失控)
max_budget_per_task = 0 # 每个任务最多花多少钱(0=无限)
enable_browser = true # 是否允许浏览器动作
max_concurrent_conversations = 3 # 最多同时几个会话
读法:
max_iterations 和 max_budget_per_task 是两条"安全带"——防止 Agent 陷入死循环无限烧钱。循环步数超过 500 就强制停;花费超过预算也停。default_agent="CodeActAgent" 指定了默认的 Agent 类型,它是 OpenHands 的招牌(Day 15 精读)。为什么要限制步数和预算? Agent 自主循环最怕"停不下来"——它可能反复尝试同一个错误方案,每一步都在调大模型(花钱)。
💥 没有它会出什么事故:假设不设上限,某次 Agent 卡在一个改不对的 bug 上反复"改代码→跑测试→失败→再改",每一轮调一次大模型(比如每轮 ¥0.5、每轮几秒)。它可能这样空转成百上千轮——一觉醒来任务没做完,账单先烧了几百块。
max_iterations/max_budget 是硬性熔断。这呼应了我们在 eino 教程里反复讲的"成本经济学"——能自主行动的 AI,必须配硬性的花费上限。💥 没有它会出什么事故:假设不设上限,某次 Agent 卡在一个改不对的 bug 上反复"改代码→跑测试→失败→再改",每一轮调一次大模型(比如每轮 ¥0.5、每轮几秒)。它可能这样空转成百上千轮——一觉醒来任务没做完,账单先烧了几百块。
max_iterations=500 和预算上限就是那道熔断闸。顺带体会 500 这个量级:一个正常任务通常几十步就收工,500 是"明显不对劲了"的红线。沿用报销制度的比喻,max_budget_per_task 就像"单次报销封顶额"——超了系统直接拒付,不问缘由。L06
[agent]:工具开关决定 Agent 的能力
[agent]
enable_cmd = true # 能跑 bash 命令(ExecuteBashAction)
enable_editor = true # 能编辑文件(FileEditorAction)
enable_browsing = true # 能浏览网页(Browser*Action)
enable_jupyter = true # 能跑 Jupyter
enable_think = true # 能记录思考(ThinkAction)
enable_finish = true # 能结束任务(FinishAction)
📝 举个例子把
enable_browsing = false 写进配置 → 会话开始的 SystemPromptEvent 里就不声明浏览器工具 → 大模型压根不知道自己能上网 → 它永远不会产出 BrowserAction。一个开关,等于给 Agent"少发一只手"。图注:一个配置旋钮,贯穿到动作真正执行(Day02+03+04 串起来)
读法:每个开关直接对应 Day 02 学的一类动作。关掉
enable_browsing,Agent 就发不出浏览器动作、系统提示里也不会声明浏览器工具——它就"失去了上网这只手"。这些开关和 Day 03 的 SystemPromptEvent.tools 是一回事:开关决定了会话开头告诉大模型"你有哪些工具"。串起来看(Day 02+03+04)
配置
enable_cmd=true → 会话开始时 SystemPromptEvent 里就包含 bash 工具的定义 → 大模型知道自己能跑命令 → 于是它可能产出 ExecuteBashAction → 沙箱执行 → 返回 ExecuteBashObservation。一条链,从配置旋钮一直贯穿到动作执行。前三天的概念在这里全串起来了。还能用 [agent.CustomAgent] + classpath 注册你自己写的 Agent。L07
[sandbox] 与 [security]
[sandbox]
base_container_image = "nikolaik/python-nodejs:..." # 沙箱基础镜像
timeout = 120 # 命令超时
volumes = "/host/path:/container/path" # 把宿主目录挂进沙箱
[security]
confirmation_mode = false # 危险动作执行前是否要人工确认
security_analyzer = "llm" # 用什么评估动作风险(默认用 LLM)
读法:
[sandbox] 定义"AI 的工作间长什么样"——用哪个 Docker 镜像(决定里面有 python/node 等什么工具)、命令超时、挂载哪些目录。[security] 则是安全闸:confirmation_mode=true 时,高危动作(还记得 Day 03 的 security_risk 吗?)执行前会暂停等你点头。security_analyzer="llm":用大模型来判断"这个动作危不危险"(对应 Day 03 ActionEvent 的
security_risk)。配合 confirmation_mode,就实现了"AI 自评风险 + 高危人工确认"的完整安全链(Day 17 深入)。沙箱隔离(物理隔离)+ confirmation(人工闸)= OpenHands 的双重安全带。L08
今日小结 + 动手
🧠 今天你应该能回答
- 三种运行方式?"双进程"(app_server + agent-server)是怎么回事?
- RUNTIME 环境变量控制什么?
- 后端真正的入口是哪个(app_server.app),server 目录现在是什么?
- [agent] 的开关如何决定 Agent 的能力?(串起 Day 02/03)
- 两条"安全带"配置?双重安全机制?
✋ 动手:读配置模板
# 1. 通读配置结构
grep -nE '^\[' config.template.toml # 列出所有配置段
# 2. 核心段
sed -n '/\[core\]/,/\[/p' config.template.toml | head -40
sed -n '/\[agent\]/,/\[/p' config.template.toml | head -30
# 3. 确认 server 只是转发壳
cat openhands/server/app.py
明天预告 · Day 05(第1周收官):把前四天串成一条完整故事线——一次任务的完整旅程:你下任务 → app_server 建会话/沙箱 → agent-server 跑 Action/Observation 循环 → 事件 webhook 回推 + 前端 WebSocket 实时展示 → 任务完成。看数据怎么在双进程间流动。