# Agent Registry → PostgreSQL 迁移 — 完整规划 > **⚠️ 2026-02-24 更新**:本文档中的 `agent_launchers` 和 `agent_cli_backends` 表已在[四维配置重构](./three-dim-refactor.md)中被删除。 > 新架构使用 `agent_clients` 表统一管理 AI 客户端配置(kimi / claude / gemini / codex)。 > `agent_definitions` 表的 `protocol / transport / launcher / cli_backend` 字段已替换为 `client / deploy / mode`。 > 详见 `docs/agent-network/04-四维正交架构.md`。 ## 1. 现状(迁移前快照) ### 存储分裂 | 数据 | 迁移前存储 | 迁移后存储 | | -------------------- | ------------------------- | ---------------------------- | | 任务/执行/事件/文件 | ✅ PostgreSQL (knex) | ✅ PostgreSQL | | 网关会话 | ✅ PostgreSQL (knex) | ✅ PostgreSQL | | Agent 定义 | ❌ `config/agents.json` | ✅ `agent_definitions` 表 | | ~~Launcher 定义~~ | ❌ `config/backends.json` | ~~已删除~~ → `agent_clients` | | ~~CLI Backend 定义~~ | ❌ `config/backends.json` | ~~已删除~~ → `agent_clients` | | Client 定义(新) | — | ✅ `agent_clients` 表 | | Agent Group 定义 | ❌ `config/agents.json` | ✅ `agent_groups` 表 | | 系统设置 | ❌ `config/agents.json` | ✅ `agent_settings` 表 | ### 消费方 | 模块 | 使用的 Registry 方法 | | ------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `agent-manager.js` | `getFullConfig()`, `getAgent()` | | `routes/agents.js` | `getFullConfig()`, `getAgent()`, `listAgents()`, `listCliBackends()`, `register()`, `unregister()`, `checkHealth()` | | `agent-config-builder.js` | 接收 `agents` 对象(从 orchestrator 传入) | --- ## 2. 目标架构 ### 设计原则 1. **单一存储源**:PostgreSQL 是唯一的持久化存储 2. **静态默认 + 用户自定义**:JSON 文件降级为 seed 数据源,首次启动时导入 DB 3. **Registry 变为 DB 代理**:所有读写走 DB,不再读 JSON 文件 4. **向后兼容**:对外 API 签名不变,内部实现切换 ### 数据流变更 ``` Before: 启动 → 读 agents.json + backends.json → 内存 config 对象 → watchFile 热重载 After: 首次启动 → 读 JSON → seed 到 DB → 删除/忽略 JSON 后续启动 → 直接从 DB 读取 UI CRUD → 直接写 DB → Registry 缓存失效 → 实时生效 ``` --- ## 3. 数据库 Schema 设计 ### 3.1 `agent_launchers` 表 ```sql CREATE TABLE IF NOT EXISTS agent_launchers ( name VARCHAR(100) PRIMARY KEY, -- 'kimi-local', 'claude-docker' type VARCHAR(20) NOT NULL, -- 'process', 'docker', 'none' command VARCHAR(200), -- 'kimi', 'claude' args JSONB DEFAULT '[]', -- ['acp'] image VARCHAR(200), -- docker image port INTEGER, -- docker port url VARCHAR(500), -- websocket url env_file VARCHAR(500), -- docker envFile volumes JSONB DEFAULT '[]', -- docker volumes is_builtin BOOLEAN DEFAULT false, -- 从 seed 导入的标记 created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); ``` ### 3.2 `agent_cli_backends` 表 ```sql CREATE TABLE IF NOT EXISTS agent_cli_backends ( name VARCHAR(100) PRIMARY KEY, -- 'claude-cli', 'gemini-cli' command VARCHAR(200) NOT NULL, -- 'claude', 'gemini' args JSONB DEFAULT '[]', -- ['--print', '--output-format', 'json'] resume_args JSONB, -- 恢复执行参数 input VARCHAR(20) DEFAULT 'arg', -- 'arg', 'stdin' output VARCHAR(20) DEFAULT 'text', -- 'text', 'json', 'jsonl' resume_output VARCHAR(20), model_arg VARCHAR(50), session_id_fields JSONB, -- ['session_id', 'conversationId'] system_prompt_arg VARCHAR(50), system_prompt_when VARCHAR(20), -- 'first', 'always', 'never' session_mode VARCHAR(20) DEFAULT 'none', -- 'none', 'always', 'existing' image_arg VARCHAR(50), image_mode VARCHAR(20), reliability JSONB DEFAULT '{}', -- watchdog 配置 is_builtin BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); ``` ### 3.3 `agent_definitions` 表 ```sql CREATE TABLE IF NOT EXISTS agent_definitions ( name VARCHAR(100) PRIMARY KEY, -- 'coder', 'verifier' type VARCHAR(30) NOT NULL, -- 'executor', 'validator', 'master', 'gateway', 'orchestrator' description VARCHAR(500), protocol VARCHAR(30), -- 'acp', 'claude-stream', null transport VARCHAR(30), -- 'pipe', 'websocket', null launcher VARCHAR(100) REFERENCES agent_launchers(name), cli_backend VARCHAR(100) REFERENCES agent_cli_backends(name), prompt_file VARCHAR(500), -- './agents/coder.md' agent_group VARCHAR(100), -- orchestrator 专用: 指向 group name is_builtin BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), CONSTRAINT agent_exec_mode CHECK ( (launcher IS NOT NULL AND cli_backend IS NULL) OR (launcher IS NULL AND cli_backend IS NOT NULL) OR (type = 'orchestrator') ) ); ``` ### 3.4 `agent_groups` 表 ```sql CREATE TABLE IF NOT EXISTS agent_groups ( name VARCHAR(100) PRIMARY KEY, -- 'development', 'novel-writing' display_name VARCHAR(200) NOT NULL, -- '软件开发' description VARCHAR(500), icon VARCHAR(50), -- 'code', 'book' is_default BOOLEAN DEFAULT false, max_repairs INTEGER, is_builtin BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); ``` ### 3.5 `agent_group_members` 表 ```sql CREATE TABLE IF NOT EXISTS agent_group_members ( group_name VARCHAR(100) REFERENCES agent_groups(name) ON DELETE CASCADE, agent_name VARCHAR(100) REFERENCES agent_definitions(name) ON DELETE CASCADE, position INTEGER DEFAULT 0, -- 排序 PRIMARY KEY (group_name, agent_name) ); ``` ### 3.6 `agent_settings` 表 ```sql CREATE TABLE IF NOT EXISTS agent_settings ( key VARCHAR(100) PRIMARY KEY, -- 'maxIterations', 'timeout' value JSONB NOT NULL, updated_at TIMESTAMP DEFAULT NOW() ); ``` --- ## 4. 实施 TODO ### Phase 1: DB Schema + Seed(后端基础) - [x] **1.1** 创建 migration SQL:`db/schema-agents.sql` - [x] 6 张表的 CREATE TABLE - [x] CHECK 约束、索引、注释 - [x] 幂等(IF NOT EXISTS) - [x] **1.2** 创建 seed 脚本:`db/seed-agents.js` - [x] 读取 `config/agents.json` + `config/backends.json` - [x] 转换为 DB 行,INSERT ... ON CONFLICT DO NOTHING(幂等) - [x] 标记 `is_builtin = true` - [x] **1.3** 启动时自动执行 - [x] 服务启动 → 执行 schema-agents.sql → 执行 seed(如果表为空) - [x] 参考现有 `db/schema.sql` 的执行模式 ### Phase 2: DB 查询层 `agent-queries.js` - [x] **2.1** 创建 `services/agent-queries.js`(对标 `task-queries.js`) - [x] `getAgent(name)` → SELECT agent_definitions + JOIN launcher/cli_backend - [x] `listAgents(filter)` → 按 type/launcher/cliBackend 过滤 - [x] `createAgent(name, config)` → INSERT - [x] `updateAgent(name, config)` → UPDATE - [x] `deleteAgent(name)` → DELETE(允许删除) - [x] `listCliBackends()` → SELECT agent_cli_backends - [x] `getCliBackend(name)` → 单个 - [x] `listLaunchers()` → SELECT agent_launchers - [x] `listGroups()` → SELECT agent_groups + JOIN members - [x] `getGroupDetail(name)` → 组 + 组内 Agent + prompt 内容 - [x] `createGroup(name, config)` → INSERT group + members - [x] `updateGroup(name, config)` → UPDATE group + REPLACE members - [x] `deleteGroup(name)` → DELETE - [x] `getSettings()` → SELECT agent_settings - [x] `getFullConfig()` → 组装完整配置(兼容旧接口) ### Phase 3: Registry 重写 - [x] **3.1** `AgentRegistry` 切换到 DB - [x] 构造函数:不再读 JSON,接受可选参数保持兼容 - [x] `load()` → 从 DB 加载到内存缓存(异步) - [x] 所有查询方法代理到 `agent-queries.js` - [x] 移除 `watchFile` 逻辑(`startWatch/stopWatch` 变为空操作) - [x] 写操作后清除缓存(`invalidateCache()` + `load()`) - [x] **3.2** 移除 `dynamicAgents` 内存存储 - [x] `register()` → 写 DB - [x] `unregister()` → 从 DB 删除 - [x] **3.3** 验证消费方兼容 - [x] `agent-manager.js` 不需要改(通过 Registry 接口访问) - [x] `agent-config-builder.js` 改为从 Registry 获取配置(保留 JSON fallback) - [x] `routes/agents.js` 的 `getFullConfig()` 返回相同结构 ### Phase 4: API CRUD - [x] **4.1** Agent CRUD - [x] `POST /api/agents` → 持久化创建(写 DB) - [x] `PUT /api/agents/:name` → 更新 Agent 配置 - [x] `DELETE /api/agents/:name` → 从 DB 删除(允许删除 builtin) - [x] 校验:type 合法、launcher 或 cliBackend 二选一存在、prompt 文件存在 - [x] **4.2** Backend CRUD - [x] `POST /api/agents/backends` → 创建 CLI Backend - [x] `PUT /api/agents/backends/:name` → 更新 - [x] `DELETE /api/agents/backends/:name` → 删除(检查引用计数) - [x] **4.3** Group CRUD - [x] `POST /api/agents/groups` → 创建 Group - [x] `PUT /api/agents/groups/:id` → 更新 Group(含成员列表) - [x] `DELETE /api/agents/groups/:id` → 删除 Group - [x] **4.4** Backend 可用性检测 - [x] `GET /api/agents/backends/health` → 对每个 CLI Backend 执行 `which ` - [x] 返回 `{ name, command, installed, version? }[]` ### Phase 5: 前端 CRUD UI - [x] **5.1** Agent 管理 - [x] AgentRegistry 页面顶部增加 "新建 Agent" 按钮 - [x] 新建表单(Drawer):name, type, description, prompt, backend 选择 - [x] AgentRow 右侧增加 "编辑" / "删除" 按钮 - [x] 编辑表单:读取当前配置 → 修改 → PUT - [x] 删除确认:Popconfirm → DELETE - [x] 操作后自动刷新列表 - [x] **5.2** Backend 健康状态 - [x] BackendPanel 每个卡片增加健康状态灯(绿/红/灰) - [x] 调用 `GET /api/agents/backends/health` 获取状态 - [x] 未安装的 CLI 显示安装提示 - [ ] **5.3** Group 管理(可选,低优先级) - [ ] Group 卡片增加 "编辑" / "删除" 按钮 - [ ] 新建 Group 表单:name, description, icon, 成员多选 - [x] **5.4** API 层 - [x] `api/index.js` 新增 CRUD 函数: - `createAgent(name, config)` - `updateAgent(name, config)` - `deleteAgent(name)` - `getBackendsHealth()` - `createAgentGroup / updateAgentGroup / deleteAgentGroup` - `createBackend / updateBackend / deleteBackend` - `resetToDefaults()` ### Phase 6: 清理 - [x] **6.1** 移除 JSON 依赖 - [x] `config/agents.json` → 仅保留为 seed 参考文件,运行时不再读取 - [x] `config/backends.json` → 同上 - [x] Registry 构造函数移除 `agentsPath` / `backendsPath` 参数 - [x] 移除 `startWatch()` / `stopWatch()` 方法(变为空操作) - [x] `task-service.js` 移除 `config` import - [x] `AgentConfigBuilder` 移除 JSON fallback,未注入 registry 时报错 - [x] **6.2** 测试 - [x] 旧测试已适配 DB 版 Registry(`registry-integration.test.js` 6/6,`e2e-registry-full.test.js` 20/20) - [x] 新增 `registry-db.test.js` — 36 项集成测试全绿 - [x] 新增 `api-agents-crud.test.js` — API CRUD 端到端测试 - [x] 前端 vite build 通过 - [x] **6.3** 数据安全 - [x] 内置 Agent 删除时弹出确认(Popconfirm 区分 builtin vs 自定义,isBuiltin 已传到前端) - [x] 恢复内置数据的 "重置为默认" 按钮 → `POST /api/agents/seed/reset` --- ## 5. 关键设计决策 ### Q1: 是否允许删除 builtin Agent? **允许**,但 UI 上增加二次确认 + "重置为默认" 恢复功能。 ### Q2: prompt 文件如何管理? **保持文件系统**。prompt 内容继续存储在 `./agents/*.md` 文件中,DB 中只存路径 (`prompt_file`)。 理由:prompt 是长文本、频繁编辑、适合 git 版本控制,不适合存 DB。 ### Q3: 是否需要缓存? **需要**。AgentRegistry 维持内存缓存(Map),写操作后标记缓存失效,下次读取重新从 DB 加载。 理由:Agent 配置读多写少,且 agent-manager 在任务执行中频繁查询。 ### Q4: 旧的 dynamicAgents 内存注册如何迁移? **废弃**。所有注册操作直接写 DB。旧的 `register()` / `unregister()` API 语义不变,底层改为 DB 操作。 ### Q5: 执行优先级顺序 **Phase 1-3** 是后端基础,必须先完成。Phase 4-5 可以增量交付。Phase 6 最后清理。 --- ## 6. 依赖关系图 ``` Phase 1 (schema + seed) ↓ Phase 2 (agent-queries.js) ↓ Phase 3 (Registry 重写) ↓ ← 此处可验证:现有功能不回归 Phase 4 (API CRUD) ↓ Phase 5 (前端 CRUD UI) ↓ Phase 6 (清理 JSON + 测试) ``` --- ## 7. 风险与回退 | 风险 | 缓解措施 | | --------------------------- | --------------------------------------- | | DB 不可用时系统无法启动 | 保留 JSON 作为 fallback(Phase 6 之前) | | 现有测试依赖 JSON 加载 | Phase 3 完成后立即跑全量测试 | | 多实例并发写 | PostgreSQL 行级锁天然支持 | | prompt 文件路径在 DB 中失效 | seed 时验证路径存在性 |