31c9958741
- 新增 scripts/db-conn.sh:按 agent id 反查中文名,读 .env 后经 MYSQL_PWD 转发给 docker exec,口令不进 argv/stdout,--print 只输出占位符 - new-agent.sh 新增 --adopt 模式(为既有 agent 补建库/账号),与 --reset-password 互斥 - AGENTS.md.tpl 与 11 个 workspace-*/AGENTS.md 改用 db-conn.sh,禁用 root 与 docker exec 直连 - mysql-schema-sync 技能改用 MYSQL_PWD_SQL 环境变量,不再硬编码密码 - 设计文档补附录 A 第 8 条:中文键名无法注入 agent 环境的三条实测依据
238 lines
14 KiB
Markdown
238 lines
14 KiB
Markdown
# agent 创建规范与数据库自动化(设计)
|
||
|
||
> **状态**:设计已确认(2026-09-16)· **待实现**
|
||
> **适用**:bk02 上的 OpenClaw(前置阅读:`bk02-openclaw-系统说明.md`)
|
||
> **决策记录**:见 §12
|
||
>
|
||
> ⚠️ 本文不含明文口令。
|
||
|
||
---
|
||
|
||
## 1. 目标与范围
|
||
|
||
**目标**
|
||
|
||
1. **main agent 在对话中即可创建新 agent**,且新 agent 的配置(`IDENTITY.md`/`SOUL.md`/`AGENTS.md`/`MEMORY.md` 及 `openclaw.json` 的 `name`/`identity.name`)**全部为中文**。
|
||
2. **每个 agent 自动获得专属 MySQL 库**(库名 = `agent_id`)+ **专用账号**(只授权自己的库,不再用 root)。
|
||
3. 新建 agent 的库**次日自动进入每日备份**,无需人工登记。
|
||
|
||
**范围**
|
||
|
||
- 新增:`new-agent.sh`(provisioning 脚本)、中文模板一组、`agent-provisioning` skill
|
||
- 改造:既有每日备份脚本 `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` 已验证的范式):
|
||
|
||
```markdown
|
||
## 数据库规范
|
||
- 默认库:`{{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 与文件操作,**不落地**
|
||
- 创建后自动自检:
|
||
1. 库存在
|
||
2. **用新账号实连一次 `SELECT 1`**(证明授权真的生效)
|
||
3. `workspace-<id>/` 四件套齐全
|
||
4. 输出可直接复制给用户的凭据行
|
||
|
||
---
|
||
|
||
## 8. 存量处置(所有 agent)
|
||
|
||
**范围:全部 12 个 agent 都要有库 + 专用账号**(含 `openclaw`)。
|
||
|
||
| 分组 | 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 不会被备份**。
|
||
|
||
**改造目标**
|
||
|
||
1. **动态发现**:备份目标 = `agents.entries` 的 id ∩ `information_schema.schemata`(排除 `mysql`/`information_schema`/`performance_schema`/`sys`),这样 provisioning 建的库**次日自动纳入**
|
||
2. **不硬编码密码**:改为从 `~/.openclaw/.env` 读(而非脚本内明文)
|
||
3. 保留策略(30 天)与 **push 到 Gitea 保持现状**(Gitea 为自建)
|
||
4. PostgreSQL(`openclaw` 库)分支保持不动
|
||
|
||
---
|
||
|
||
## 11. 技能强制性 = 档 ①(纯规范,不改环境)
|
||
|
||
**已明确接受的前提**:不剥夺凭据、不收窄 sudo、不启用 exec 审批。因此 main **技术上仍可能**从 `.env` / 备份脚本取得 root 自行建库 —— 规范能大幅降低概率,但**不能归零**。
|
||
|
||
在纯规范档下,把约束强化到六点:
|
||
|
||
1. **写入 main 的「红线」章节**(`workspace/AGENTS.md` 已有该章节):
|
||
> 创建 agent 必须且只能执行 `bash ~/.openclaw/scripts/new-agent.sh <id> <中文名>`;
|
||
> 禁止自行拼 SQL、禁止用 `mysql`/`docker exec` 建库、禁止为建库读取任何 root 凭据。
|
||
2. **SKILL.md 的 `description` 精确化**(skill 触发的唯一依据):写明触发场景与关键词("新建 agent""创建 agent""加一个 agent")。
|
||
3. **让正确路径比绕过更省事**(行为引导):脚本一条命令完成 9 步;绕过则需自行拼 SQL、处理 `.env`、写四个中文模板 —— 复杂度差本身就是约束。
|
||
4. **脚本内不硬编码密码**(见 §6)。
|
||
5. **脚本自带轻量审计**:每次执行追加一行到 `workspace/db/agent-provision.log`;**不改环境**,但事后可对账 —— 库中多出的账号若不在日志里,即为绕过证据。
|
||
6. **阻断态显式提示**:出现"库已存在但账号缺失"等异常时,输出"请改用 `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 |
|
||
|
||
---
|
||
|
||
## 附:实现时需现场确认的两点
|
||
|
||
1. `openclaw agents add` 生成的骨架文件清单与内容 —— 决定模板是**覆盖**还是**补充**
|
||
2. `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=…`
|