- 系统说明:agent 清单 12→11(去已废弃的 openclaw);库规模 36→35(其 sqlite 已移入 backups/removed-20260916/) - 设计文档 §8:范围 12→11,去掉「含 openclaw」 - 卷儿检查报告:agent 数量 12→11 - 配套但不入 git 的动作:.env 删除 6 个零引用的旧中文名键(卷儿记账/理财财务/健身健康/后端开发/简历管理/旅行规划,现为 11 个新键)、删除空目录 workspace-attestations/、agents/openclaw/ 移至 backups/
14 KiB
agent 创建规范与数据库自动化(设计)
状态:设计已确认(2026-09-16)· 待实现 适用:bk02 上的 OpenClaw(前置阅读:
bk02-openclaw-系统说明.md) 决策记录:见 §12⚠️ 本文不含明文口令。
1. 目标与范围
目标
- main agent 在对话中即可创建新 agent,且新 agent 的配置(
IDENTITY.md/SOUL.md/AGENTS.md/MEMORY.md及openclaw.json的name/identity.name)全部为中文。 - 每个 agent 自动获得专属 MySQL 库(库名 =
agent_id)+ 专用账号(只授权自己的库,不再用 root)。 - 新建 agent 的库次日自动进入每日备份,无需人工登记。
范围
- 新增:
new-agent.sh(provisioning 脚本)、中文模板一组、agent-provisioningskill - 改造:既有每日备份脚本
backup-agent-dbs.sh(硬编码 → 动态发现) - 不修改 OpenClaw 自身(纯配置/文件层扩展)
明确不做:见 §9。
2. 命名与标识规范
| 概念 | 形式 | 示例 |
|---|---|---|
agent_id |
ASCII slug,正则 ^[a-z][a-z0-9-]{0,23}$ |
reading |
| 中文名 | 2–6 个汉字 | 阅读 |
| MySQL 库 | = agent_id |
reading |
| MySQL 账号 | agent_<id> |
agent_reading |
| 密码变量 | DB_PASSWORD_<中文名>(写入 ~/.openclaw/.env) |
DB_PASSWORD_阅读 |
| workspace | ~/.openclaw/workspace-<id> |
workspace-reading |
| agentDir | ~/.openclaw/agents/<id>/agent |
— |
中文只用于展示层(name、identity.name、文档正文、.env 变量名后缀)。
id / 库名 / 账号 / 目录名一律 ASCII —— 依据:既有的 Obsidian 与 obsidian(仅大小写不同)曾导致 openclaw backup create --verify 因 portable path collision 失败。
3. 组件与职责
| 组件 | 路径 | 职责 |
|---|---|---|
| provisioning 脚本 | ~/.openclaw/scripts/new-agent.sh |
唯一特权入口:建库、建账号、授权、写 .env、创建 agent、渲染模板、设置中文名、自检 |
| 中文模板 | ~/.openclaw/scripts/agent-templates/{IDENTITY,SOUL,AGENTS,MEMORY}.md.tpl |
新建 agent 的文档骨架 |
| skill | ~/.openclaw/skills/agent-provisioning/SKILL.md |
中文;定义触发词、参数、约束、输出格式;约束 main 只能调用脚本 |
| 审计日志 | ~/.openclaw/workspace/db/agent-provision.log |
脚本每次执行追加一行(时间/参数/结果),供事后对账 |
| 备份脚本(改造) | /usr/local/bin/backup-agent-dbs.sh |
见 §10 |
设计要点
- 特权动作(root 建库)只存在于脚本内;main 的 skill 里写死"只允许调用脚本"。
- skill 与
~/.openclaw同处一个 git 仓库,规范可版本化、可追溯。
4. 数据流(main 创建 agent)
用户:“新建一个 agent,id 用 reading,中文名 阅读”
↓
main 读 skills/agent-provisioning/SKILL.md(约束 + 流程)
↓
main 执行:bash ~/.openclaw/scripts/new-agent.sh reading 阅读
↓
脚本(幂等,逐步输出):
① 入参校验:id 正则 / 中文名非空且长度合法
② 冲突检查:库、agent 条目、workspace 目录任一已存在 → 失败退出(见 §6)
③ 生成 32 位随机密码
④ MySQL(root 凭据取自 ~/.openclaw/.env 的 MYSQL_PWD_ROOT):
CREATE DATABASE `<id>` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
CREATE USER 'agent_<id>'@'%' IDENTIFIED BY '<随机密码>';
GRANT ALL PRIVILEGES ON `<id>`.* TO 'agent_<id>'@'%';
⑤ 写入 ~/.openclaw/.env:DB_PASSWORD_<中文名>='<随机密码>'
(同键则替换,不重复追加;.env 已在 .gitignore)
⑥ openclaw agents add <id> --workspace ~/.openclaw/workspace-<id> --non-interactive
⑦ 渲染中文模板到 workspace-<id>/(覆盖 openclaw 生成的英文骨架)
⑧ openclaw agents set-identity --agent <id> --from-identity
(从 IDENTITY.md 读中文名写入配置)
⑨ 自检(§7)并输出汇总:库名 / 账号 / 凭据变量 / workspace / 下一步
为什么用 --from-identity:中文名先落在 IDENTITY.md(人可读),再一条命令同步进 openclaw.json,避免两处手工维护不一致。
5. 中文模板
四件套(全中文,.tpl 内用占位符 {{AGENT_ID}}、{{AGENT_NAME}}):
IDENTITY.md—— 我是谁(含中文名、emoji)SOUL.md—— 性格与表达风格AGENTS.md—— 工作规范(核心是数据库段)MEMORY.md—— 长期记忆骨架
AGENTS.md.tpl 的数据库段(沿用 workspace-sql 已验证的范式):
## 数据库规范
- 默认库:`{{AGENT_ID}}` —— 这就是你自己的库
- 账号:`agent_{{AGENT_ID}}`
- 连接(推荐):`~/.openclaw/scripts/db-conn.sh {{AGENT_ID}}`(交互式)或 `db-conn.sh {{AGENT_ID}} --sql "select database()"`
- 口令:由该脚本自动从 `~/.openclaw/.env` 读取(键名 `DB_PASSWORD_{{AGENT_NAME}}`),口令不会出现在命令行中
- 禁止:使用 root、使用 docker exec、访问其他 agent 的库、跨库写入
- 破坏性语句(DROP / TRUNCATE / 无条件 DELETE)必须先请示
6. 幂等、错误处理与安全
| 场景 | 行为 |
|---|---|
| id 非法 / 中文名为空 | 立即失败并打印用法 |
| 库或 agent 或 workspace 已存在 | 默认失败(不覆盖、不改动);仅 --reset-password 才重置账号密码 |
| 中途失败 | 已完成的步骤保留;重跑时明确提示"已完成到第 N 步",不静默改写 |
.env 写入 |
幂等替换(同键覆盖,避免重复行) |
| SQL 注入 | id 必须通过白名单正则后才参与拼接 |
| 凭据 | 脚本不硬编码密码,从 ~/.openclaw/.env 读;生成的密码只写 .env |
7. 验证方式
new-agent.sh --dry-run:打印将执行的 SQL 与文件操作,不落地- 创建后自动自检:
- 库存在
- 用新账号实连一次
SELECT 1(证明授权真的生效) workspace-<id>/四件套齐全- 输出可直接复制给用户的凭据行
8. 存量处置(所有 agent)
范围:全部 11 个 agent 都要有库 + 专用账号(原写 12,含已废弃的 openclaw;2026-09-16 校正)。
| 分组 | agent | 动作 |
|---|---|---|
| 已有库,需改造 | main juaner wellness tab finances fitness resume travel |
建 agent_<id> 账号 + 授权 + 改各自 AGENTS.md 数据库段(由 root 直连改为专用账号) |
| 缺库需新建 | note(笔记) pbs(PBS备份) |
建库建号 + 补文档 |
| 特例 | sql(SQL) |
保留其远程 VPS 业务库定位,另给一个私有库做工作区 |
| 待纳入 | openclaw |
当前不在 agents.entries/agentToAgent.allow;按「所有 agent」口径纳入并建库建号(纳入前确认不影响既有行为) |
存量改造是一次性迁移,建议作为独立步骤执行(含改
.env变量与各 agent 文档),与新建流程分开验证。
9. 非目标(YAGNI)
- 不做可视化界面 / 批量重建工具
- 不做库结构版本迁移工具
- 不去改 skill 机制使其按 agent 隔离 —— OpenClaw 的
skills.entries是全局开关(52 项,仅enabled),无法按 agent 配置;权限隔离由 MySQL 账号承担(这也是"专用账号"从建议变为必需的原因)
10. 每日备份改造
现状(已存在,非自建部分):crontab 0 3 * * * /usr/local/bin/backup-agent-dbs.sh
- 逐库
mysqldump --single-transaction --routines --triggers --events,用 root - 备份到各自 workspace 的
db/(如workspace-resume/db/resume_20260916.sql.gz),另留一份*_latest.sql - 清理 30 天前备份
- 结束后
git commit+ push 到自建 Gitea
问题:库清单是硬编码的 8 条 DB_MAP ⇒ 新建 agent 不会被备份。
改造目标
- 动态发现:备份目标 =
agents.entries的 id ∩information_schema.schemata(排除mysql/information_schema/performance_schema/sys),这样 provisioning 建的库次日自动纳入 - 不硬编码密码:改为从
~/.openclaw/.env读(而非脚本内明文) - 保留策略(30 天)与 push 到 Gitea 保持现状(Gitea 为自建)
- PostgreSQL(
openclaw库)分支保持不动
11. 技能强制性 = 档 ①(纯规范,不改环境)
已明确接受的前提:不剥夺凭据、不收窄 sudo、不启用 exec 审批。因此 main 技术上仍可能从 .env / 备份脚本取得 root 自行建库 —— 规范能大幅降低概率,但不能归零。
在纯规范档下,把约束强化到六点:
- 写入 main 的「红线」章节(
workspace/AGENTS.md已有该章节):创建 agent 必须且只能执行
bash ~/.openclaw/scripts/new-agent.sh <id> <中文名>; 禁止自行拼 SQL、禁止用mysql/docker exec建库、禁止为建库读取任何 root 凭据。 - SKILL.md 的
description精确化(skill 触发的唯一依据):写明触发场景与关键词("新建 agent""创建 agent""加一个 agent")。 - 让正确路径比绕过更省事(行为引导):脚本一条命令完成 9 步;绕过则需自行拼 SQL、处理
.env、写四个中文模板 —— 复杂度差本身就是约束。 - 脚本内不硬编码密码(见 §6)。
- 脚本自带轻量审计:每次执行追加一行到
workspace/db/agent-provision.log;不改环境,但事后可对账 —— 库中多出的账号若不在日志里,即为绕过证据。 - 阻断态显式提示:出现"库已存在但账号缺失"等异常时,输出"请改用
new-agent.sh"的明确指引。
12. 决策记录
| # | 决策点 | 结论 | 日期 |
|---|---|---|---|
| 1 | 落地形态 | 方案 B:脚本 + skill,由 main 调用(否决纯人工脚本、否决把 root 交给 main 自行拼 SQL) | 2026-09-16 |
| 2 | 目标库与账号 | 本机 docker MySQL;每个 agent 专用账号(停用 root 直连) | 2026-09-16 |
| 3 | 存量范围 | 所有 agent(含 openclaw)都要有库与账号 |
2026-09-16 |
| 4 | 中文范围 | 展示层全中文;id/库名/账号/目录名保持 ASCII |
2026-09-16 |
| 5 | 备份 | 改造为动态发现;保持 push 到自建 Gitea | 2026-09-16 |
| 6 | 技能强制性 | 档 ①(纯规范):不改凭据、不收窄 sudo、不加 exec 审批 | 2026-09-16 |
| 7 | 授权模型 | skill 层无法按 agent 隔离(skills.entries 全局)⇒ 隔离必须由 MySQL 账号承担 |
2026-09-16 |
附:实现时需现场确认的两点
openclaw agents add生成的骨架文件清单与内容 —— 决定模板是覆盖还是补充set-identity --from-identity对IDENTITY.md格式的具体要求(字段名/解析规则)
附录 A:实现期间的实测发现(2026-09-16)
本节回答 §「附:实现时需现场确认的两点」,并记录实现中撞出的偏差与处置。
| # | 发现 | 处置 |
|---|---|---|
| 1 | .env 中没有 MYSQL_PWD_ROOT(真实键名是 MYSQL_PWD);且计划里的 grep 写法在 set -euo pipefail 下会因无匹配静默退出(连报错都没有) |
脚本抽出 read_env_var() 并做 MYSQL_PWD_ROOT → MYSQL_PWD 回退 |
| 2 | openclaw agents add 生成 AGENTS/BOOTSTRAP/IDENTITY/SOUL/USER.md + 一个 .git 仓库,且不生成 MEMORY.md/memory/;存量 12 个 workspace 都没有 .git |
脚本渲染 5 个中文模板(补齐 MEMORY/USER),并删除 BOOTSTRAP.md(身份已定,避免首启"起名仪式")与 .git(避免嵌套仓库) |
| 3 | set-identity --from-identity 只认英文标签 - Name: xxx,对中文模板的 - **名称:** 解析失败(No identity data found) |
设计目标(配置中存中文名)由 --name 兜底达成;脚本保留兜底分支。这不是 bug,是既定偏差 |
| 4 | 存量 .env 里 6 行 DB_PASSWORD_<中文名> 的值与 MySQL root 密码相同 |
意味着现状下"每 agent 一账号"并未真正隔离。Task E 将生成独立随机口令并替换存量行 |
| 5 | 同机 3 个 workspace-{juaner,tab,wellness}/db 属主为 root → 这 3 个库自 2026-08-26 起每天备份失败 |
已 chown 修复;备份脚本验收明确改为不加 sudo(生产路径是 yangxuan 的 crontab;用 sudo 跑正是该事故成因) |
| 6 | agents delete 会连 workspace 一起删,但留下 ~/.openclaw/agents/<id>/ 空壳 |
清理流程需手工 rm -rf(已写入脚本注释与计划文档) |
| 7 | 中文名含 & 时,调用侧若不加引号会被 shell 解析;实测已导致一次意外创建 |
已回灌进 agent-provisioning skill:必须写成 <id> '<中文名>' |
| 8 | 中文键名不可能作为环境变量暴露给 agent —— 三条实测依据:(a) gateway 进程 /proc/<pid>/environ 里只有 MYSQL_PWD_ROOT/DB_PASSWORD_ROOT,没有 DB_PASSWORD_<中文名>;(b) service 的 OPENCLAW_SERVICE_MANAGED_ENV_KEYS 是白名单,只列 9 个键、不含 DB_PASSWORD_*,而键数随 agent 数量增长,维护白名单不现实;(c) 官方把凭据注入 agent 执行环境的机制 openclaw secrets store --kind env 要求名称匹配 ^[A-Z][A-Z0-9_]{0,127}$(纯大写 ASCII),中文键名语法上就不合法 |
⇒ 以「环境变量」形式暴露中文键名不可行,改用统一入口 ~/.openclaw/scripts/db-conn.sh <agent_id> [--sql "<SQL>"] [--print]:按 id 反查 agents.entries.<id>.name → 读 .env 的 DB_PASSWORD_<中文名> → 经 MYSQL_PWD 环境变量注入 docker exec(口令不进 argv、不进 stdout;--print 只打印占位符)。已同步:11 个 AGENTS.md、AGENTS.md.tpl、new-agent.sh 汇总输出、本文 §5 示例。§2 命名规范里的「密码变量 DB_PASSWORD_<中文名>」依然成立,但它的定位是 .env 内的存储键名,不再是 agent 的环境变量 |
审计日志格式(Task E 解析用):
[时间] id=… name=… user=… mode=create|reset-password selfcheck=… dry_run=…