这篇按五步走一遍:init 出声明文件 → validate 校验 → 摊成目录工程 → 版本预览与回滚 → plan 预演变更。命令、输出、退出码全部出自 2026-09-30 一轮实测(12:56 到 13:30 收工),环境是本机 bl 2.1.0,全程 0 元、0 云资源,末尾附 12 条可直接抄的复现清单,中间夹 11 个常见问题的实答。
一、环境准备:先把命令面数清楚
bl 有版本差异,别拿旧记忆敲命令。本机原来是 2.0.1,bl update 升到 2.1.0(2026-09-28 发布)、skills 同步之后,我第一件事是重读 bl managed-agent --help,数出来三组数字:
- 72 个子命令
- 7 类资源:Agent / Environment / Skill / Vault / Deployment / Session / File
- 14 条标 No Auth,不登录就能跑完声明、校验、版本、回滚这条主干
一条叶子命令的帮助也可能查不到:bl managed-agent project build --help 会回 Unknown command,要回到组级 bl managed-agent project --help 才列得出命令。写脚本前建议以组级 help 为准。
装 CLI 两条路:走官方 skill 包让 coding agent 代跑,或终端 npm install -g bailian-cli;要往下走到 plan 和 apply,得先签一个 API Key,取值方式见密钥文档。
资源类型和"全天候待命"那类智能体的部件能一项项对上:云端计算机对应 environments(config.type 合法值只有 cloud 与 self_hosted,networking.type 只有 unrestricted 与 limited),应用插件对应 skills 与 tools.builtin,凭据对应 vaults,排程与按停对应 deployment 和 deployment pause|unpause,过程汇报对应 session events 与 session event stream,素材对应 file upload|download。

二、第一步:init 生成声明文件
bash
bl managed-agent init
一次落两个文件:agents.yaml 和 .gitignore。密钥不落文件,providers.bailian.api_key 写 ${DASHSCOPE_API_KEY}、base_url 写 ${BAILIAN_BASE_URL},执行时插值取环境变量。.gitignore 自动写三行:agents.state.json(本地状态记录)、.openagentpack/versions/(版本快照)、.env,正是一个 Agent 工程最容易被顺手提交出去的三样。
模板里 Agent 的形状是 description / model / instructions / environment / tools.builtin 五项,默认模型 qwen3.8-max,环境引用同文件里定义的 dev。
三、第二步:validate 拦下 6 类、放行 4 类
bl managed-agent validate 的 help 写着 Runs fully offline against local files: no login or provider credentials required. 这句话决定了它的边界。我造了 10 个坏法挨个喂它:
| 改坏的地方 | 退出码 | 返回 |
|---|---|---|
model: qwen3.8-max-not-real |
0 | Configuration is valid. |
tools.builtin: [bash, teleport] |
0 | Configuration is valid. |
environment: staging(未定义) |
1 | [config.agent.environment.unknown] references unknown environment 'staging' |
version: "2"(模板是 "1") |
0 | 放行,补测 "0"、"99" 一样放行 |
删掉 instructions |
2 | instructions: Invalid input: expected string, received undefined |
agents: [unclosed |
2 | YAML parse error: ... at line 19, column 10 |
agents: {} |
0 | 放行 |
networking.type: open-internet |
2 | `Invalid option: expected one of "unrestricted" |
api_key: ${TOTALLY_UNSET_VAR_XYZ} |
2 | Environment variable 'TOTALLY_UNSET_VAR_XYZ' is not set |
config.type: onprem |
2 | `Invalid option: expected one of "cloud" |
结论是拦下 6 类、放行 4 类。拦的是形状与交叉引用:必填字段、枚举取值、YAML 语法、未设置的环境变量、引用了同文件里不存在的环境。放行的是目录与语义存在性:模型名、工具名、version 取值、空 agents。它拿不到模型目录和工具注册表,所以放行那四类不是漏,是离线这个前提下的边界。
provider 白名单硬拦,报错给中英两份;type: self_hosted 被拒时连修法一起给:[bailian.environment.self_hosted.unsupported] environment.dev: self_hosted environments are supported only by Qoder BYOC; use type 'cloud' or pin this environment to the qoder provider.

四、第三步:摊成目录工程,--project . 必带
bash
bl managed-agent project init --project .
它把单文件 YAML 摊成一个目录:agents/assistant/agent.json 放资源声明,提示词外置到 agents/assistant/instructions.md(agent.json 里以相对路径引用),环境放 agents/assistant/environments/dev/。提示词写回 agent.json 会被拒:project.directory.invalid: agents/assistant/agent.json: instructions must be stored in instructions.md.
漏掉 --project . 会丢配置。我先用 bl managed-agent init 生成 YAML,把 model 改成 qwen3.8-max-CUSTOM、描述改成 CUSTOM DESCRIPTION,再在同一目录跑不带参数的 project init,返回 converted_from_yaml: false,另外建了一个 managed-agent/ 子目录的全新工程,自定义一个字没带过去。补上 --project . 才回 true,model、description、environment、tools 全部落进 agent.json。
两次返回的差别全在 converted_from_yaml 这一个布尔值上。project_root、baseline_version、state_migrated 这些字段照常填好、命令照常成功,所以这一步没有失败信号,只有这一个字段能看。批量做工程化迁移时,建议在脚本里把它当断言读。
五、第四步:版本链、预览与回滚
.openagentpack/versions/project/ 里是 blobs/、entries/、manifests/ 三层,文件名全是 SHA-256,属于内容寻址存储。一条 entry 的字段:
json
{
"version_id": "ff4f7e25fe93b355...705c712d",
"short_version": "ff4f7e25fe93",
"parent_version": null,
"tree_hash": "b174bc9558c1b11a...80cb7f9e",
"yaml_hash": "e1acb6c4ccc5be29...731f327b",
"manifest_hash": "70144c391ff12ca3...dff7f167e",
"message": "Initialize project",
"created_by": "<本机用户名>",
"created_at": "2026-09-30T05:11:01.644Z",
"nonce": "7ec4ee49-0f67-4fc7-a918-dc417a5fc800"
}
parent_version 串成链、tree_hash 锁内容、created_by 记人、message 记改动原因,version status 另外带 write_blockers 与 restore_blockers 两个门禁数组。
回滚这条链我实测跑通:把 agent.json 的 model 改成 broke-during-iteration,project version status 从 source_status: clean 变 modified,project_revision 换哈希;version preview --version-id ec7ebbc286cb... 渲染出 before_yaml;version restore --version-id ec7ebbc286cb... --yes 跑完,model 回到 QWEN-CUSTOM-MODEL,source_status 回到 clean。
restore 的帮助文本标了风险,读全它:
Risk: high
Risk message: This restores the full directory source to the working tree.
Version history and remote State will not move.
版本历史与云端 State 不跟着回滚,本地回滚不等于线上回滚。线上要退回去得再走 apply,官方文档对它的描述是"先刷新已管理资源的云端状态,再计算变更计划,按依赖关系创建或更新资源,删除已从配置中移除且由当前项目管理的云端资源,并同步本地状态记录",执行前要加 --yes。本轮零成本约束下我没执行 apply。
project build 也不推进版本链:build 完 version list 仍是 1 条。build 只整理目录源、生成不可变的 Publish Build,要把版本记进链子得 publish,而 publish 要 API Key。

六、第五步:plan 预演,顺手自证只读
Planned actions:
+ environment.dev (bailian)
+ agent.assistant (bailian)
Plan: 2 to create, 0 to update, 0 to destroy.
+ 是新建,另两栏是更新与销毁计数,terraform 的说法。两个开关:--no-refresh 跳过刷新,--refresh-only 只刷状态看漂移、不出变更计划。这条命令要 Key,但不建资源。我跑完立刻执行 bl managed-agent state list,返回 No resources tracked in state.,这就是 0 元、0 云资源的复核证据。

七、常见问题
Q1:validate 会查模型名、工具名是否真的存在吗?
不查。model: qwen3.8-max-not-real 和 tools.builtin: [bash, teleport] 两例都返回 Configuration is valid.,退出码 0。原因是它完全离线,拿不到模型目录与工具注册表。目录核对要留到能联网的那一步。
Q2:退出码 0 / 1 / 2 分别代表什么?
三层协议:0 通过;1 是语义诊断,消息带 [...] 错误码前缀并进 diagnostics[] 数组;2 是模式与解析层的拒绝,没有错误码前缀(缺 instructions、YAML 语法错、枚举取值错、环境变量未设置都落在这里)。CI 想区分"配置写错了"和"命令用法不对",读退出码就够。
Q3:失败态的 validate --output json 能直接喂 jq 吗?
能。我原本以为失败时会把人读文本和机读 JSON 混在 stdout 里吐出来,分开两个流才发现错了:stdout 是合法的单一 JSON 文档,{"error": {...}} 信封走 stderr,bl managed-agent validate --output json | jq .diagnostics 直接可用。
Q4:两个校验命令的 JSON 能共用一套解析逻辑吗?
不能。project validate --output json 是另一套键名:project_root、project_revision、source_manifest_hash、yaml_hash、diagnostics、warnings、organization_moves、after_yaml,没有 valid 这个键。按前者写判断的脚本读到后者会拿到 null。
Q5:一次塞进多处错误,它会一条一条报吗?
会汇总。我一次填两个不存在的 skill,两条 config.agent.skill.unknown 全部进数组,不做 fail-fast。
Q6:version 字段写 "0"、"99" 会被拦吗?
不会,一律放行。这个字段目前不参与校验,别指望它做版本兼容闸门。
Q7:project init 之后我原来 YAML 里的自定义配置去哪了?
不带 --project . 时哪也没去,它另建了一个 stock 工程,返回 converted_from_yaml: false,你的自定义值被丢掉。带 --project . 才回 true 并带入。另外两个脚手架默认模型不一致:managed-agent init 的模板是 qwen3.8-max,project init 建的目录工程是 qwen3.7-max。
Q8:回滚了配置,云上跑着的 Agent 会跟着退回去吗?
不会。restore 帮助原文是 Version history and remote State will not move. 它只把目录源码恢复到工作区。
Q9:project build 会产生新版本吗?
不会,build 后 version list 仍是 1 条。记版本要 publish,需要 API Key。
Q10:file 资源的 source 指向不存在的本地文件,校验发现得了吗?
发现不了。指向 ./definitely-missing.md 时 project validate 返回 Project is valid (5e5ad44f0ec6)。文件在不在是离线就能查的事实,它跳过了;同一类引用里未知 skill 是拦的,所以属于覆盖面缺口。流水线里补一步文件存在性检查。
Q11:哪些命令这条链路没跑?
apply、publish、session create、deployment run 一条没跑,它们会建持久资源、真调模型、产生计费。因此 Agent 上线要等多久、session run 的流式响应长什么样、deployment 排程实际延迟、模型名写错会不会在 apply 阶段被打回,这四个问题本篇没有答案。
八、完整复现清单
bash
bl managed-agent init
bl managed-agent validate
bl managed-agent validate --output json
bl managed-agent project init --project .
bl managed-agent project validate
bl managed-agent project build
bl managed-agent project version status
bl managed-agent project version list
bl managed-agent project version preview --version-id <full-version-id>
bl managed-agent project version restore --version-id <full-version-id> --yes
bl managed-agent state list
bl managed-agent plan # 需要 API Key,只读
前 11 条不登录、不花钱、不建资源,最后一条要 Key。装 CLI 走官方 skill 包,或直接 npm install -g bailian-cli;要走到 apply 先去签 Key。资源与参数语义以 managed-agent CLI 文档为准。