From bb5eef28298456e61b4da47045b425df53d1a062 Mon Sep 17 00:00:00 2001 From: dangpeng Date: Tue, 16 Jun 2026 16:51:29 +0800 Subject: [PATCH 1/4] docs(design): add shared work queue ADR and update runtime model references Add ADR-0003 for the JSONL file-based shared work queue with `san queue` subcommand. Replace all `san agent run --persona` references with `san --persona -p` in ADR-0002, reflecting the existing print-mode headless agent (PR #231). Co-Authored-By: Claude Opus 4.7 Signed-off-by: dangpeng --- .../0002-autonomous-dev-management.md | 708 +++++++++++------ .../decisions/0003-shared-work-queue.md | 312 ++++++++ .../0002-autonomous-dev-management.md | 725 ++++++++++++------ .../decisions/0003-shared-work-queue.md | 325 ++++++++ 4 files changed, 1619 insertions(+), 451 deletions(-) create mode 100644 docs/cn/design/decisions/0003-shared-work-queue.md create mode 100644 docs/design/decisions/0003-shared-work-queue.md diff --git a/docs/cn/design/decisions/0002-autonomous-dev-management.md b/docs/cn/design/decisions/0002-autonomous-dev-management.md index 84445091..5b2a898e 100644 --- a/docs/cn/design/decisions/0002-autonomous-dev-management.md +++ b/docs/cn/design/decisions/0002-autonomous-dev-management.md @@ -2,7 +2,7 @@ ## 状态 -提案中 — 2026-06-11. +提案中 — 2026-06-11. 更新于 2026-06-16. ## 背景 @@ -26,14 +26,13 @@ ``` genai-io(Org,已存在) ├── san ← San 源码仓库(已存在,被管理的目标项目) -└── san-team ← 新建的仓库(San 开发团队 Persona) +└── san-team ← 新建的仓库(纯内容,无 Go 代码) ├── leader/ │ ├── system/ │ │ ├── identity.md │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ - │ │ └── ... │ └── settings.json ├── dev/ │ ├── system/ @@ -41,7 +40,6 @@ genai-io(Org,已存在) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ - │ │ └── ... │ └── settings.json ├── qe/ │ ├── system/ @@ -49,21 +47,21 @@ genai-io(Org,已存在) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ - │ │ └── ... │ └── settings.json - └── release/ - ├── system/ - │ ├── identity.md - │ ├── behavior.md - │ └── rules.md - ├── skills/ - │ └── ... - └── settings.json -``` - -- **`san-team`**:新建仓库,包含 San 项目的开发团队。 - Persona 直接放在仓库根目录下。如果其他项目需要类似的自主管理, - 可以创建独立的团队仓库(如 `devops-team`)。 + ├── release/ + │ ├── system/ + │ │ ├── identity.md + │ │ ├── behavior.md + │ │ └── rules.md + │ ├── skills/ + │ └── settings.json + ├── state/ ← queue.jsonl 存放位置 + └── run.sh ← 轮询循环(12 行 shell 脚本) +``` + +- **`san-team`**:纯内容仓库 —— 无 Go 代码、无二进制。只包含 + persona markdown 文件、共享状态目录和轮询 shell 脚本。 + 所有 Go 逻辑都在 San 中,作为可复用的独立功能。 - **团队(Team)**:`san-team` 仓库中的一组 Persona, 为特定目标协同工作 —— 管理 San 项目的 issue、feature、bug 和发布。 - **Persona 目录**:每个 Persona 遵循 persona 规范的三层结构: @@ -73,127 +71,215 @@ genai-io(Org,已存在) ## 运行模型 -每个 Persona 以**独立的 San 实例**运行: +每个 Persona 以**独立的 San 实例**运行。根据角色不同,启动方式有区别: + +### Leader —— 交互式 TUI + +Leader 是管理员的对话入口,以标准交互式 San 会话运行: ```bash -# 启动 Leader Persona(管理员交互入口) -san start --persona leader --team san-team +san --persona leader +``` -# 启动 Dev Persona(等待编码任务) -san start --persona dev --team san-team +使用 San 现有的 `--persona` 参数,从 `~/.san/personas/leader/` +(指向 `san-team/leader/` 的软链接)加载 persona 配置。 -# 启动 QE Persona(等待验证任务) -san start --persona qe --team san-team +### Dev, QE, Release —— 通过 run.sh 无头运行 + +这些 persona 通过 `run.sh` 启动,以 `san --persona -p` 方式无头运行。 +不需要交互式终端 —— 它们轮询队列、领取任务、执行任务、更新队列: + +```bash +# 终端 2:启动 Dev 轮询循环 +./run.sh dev /path/to/san -# 启动 Release Persona(等待发布任务) -san start --persona release --team san-team +# 终端 3:启动 QE 轮询循环 +./run.sh qe /path/to/san + +# 终端 4:启动 Release 轮询循环 +./run.sh release /path/to/san +``` + +`run.sh` 是一个简单的 shell 脚本(见下文)。每个 persona 的系统提示词 +(identity + behavior + rules)由 `san --persona -p` 从 +`~/.san/personas//` 加载。 + +### 安装:链接 Persona + +启动前,将 san-team 中的 persona 链接到 San 的 persona 目录: + +```bash +ln -s /path/to/san-team/leader ~/.san/personas/leader +ln -s /path/to/san-team/dev ~/.san/personas/dev +ln -s /path/to/san-team/qe ~/.san/personas/qe +ln -s /path/to/san-team/release ~/.san/personas/release ``` -`--persona` 参数告诉 San 在启动时加载哪个 Persona 目录的配置 -(system/ + skills/ + settings.json),不需要在运行中通过 `/persona` 命令切换。 +所有 persona —— Leader、Dev、QE、Release —— 统一使用 `~/.san/personas/` 机制。 +不需要额外的 persona 加载代码。 -多个 Persona 实例可以同时运行在不同的终端、容器或机器上。 -它们通过共享工作队列(`san-team/state/queue.jsonl`)协调工作。 +## run.sh —— 粘合层 + +`run.sh` 是 san-team 中唯一的"代码"。它是 `san queue` 和 `san -p` 之间的桥梁: + +```bash +#!/bin/bash +set -euo pipefail +TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" +PERSONA="${1:?usage: $0 }" +CWD="${2:-$(pwd)}" +INTERVAL="${3:-30}" + +echo "[san-team:$PERSONA] 每 ${INTERVAL}s 轮询一次, cwd=$CWD" + +while true; do + # 1. 原子认领下一个 pending 任务 + TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) + if [ -n "$TASK" ]; then + ID=$(echo "$TASK" | jq -r '.id') + TITLE=$(echo "$TASK" | jq -r '.title') + DESC=$(echo "$TASK" | jq -r '.description') + echo "[san-team:$PERSONA] 认领 $ID: $TITLE" + + # 2. 构建任务提示词,无头运行 Agent + PROMPT="Task: $TITLE + + $DESC + + 完成后,总结你的工作内容和 PR 链接。" + + if san --persona "$PERSONA" -p "$PROMPT"; then + san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] 完成 $ID" + else + san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] 释放 $ID(将重试)" + fi + fi + sleep "$INTERVAL" +done +``` + +关键设计要点: +- **队列操作是 shell 的职责**:`run.sh` 调用 `san queue claim`、 + `san queue complete`、`san queue release`。LLM Agent 完全不知道队列的存在。 +- **Persona 加载是 San 的职责**:`san --persona -p` 从 + `~/.san/personas/` 加载 persona 系统提示词。Shell 不需要拼装 persona 文件。 +- **LLM 推理是 San 的职责**:`san -p` 用任务描述作为用户消息来执行 Agent。 +- **Shell 只是胶水**:认领 → 运行 → 完成/释放。12 行代码。 ## 工作流程 -管理员只跟 Leader Persona 对话。Leader 把需求拆成 Task 写入共享队列, -其他 Persona 从队列领取任务,完成后更新队列状态。 +管理员只跟 Leader Persona 对话。Leader 把需求拆成 Task, +通过 `san queue add` 写入共享队列。其他 Persona 通过 +`run.sh` 轮询队列,认领匹配的任务,完成后更新队列状态。 ``` 管理员(人) │ - │ "实现用户登录功能" or "修复所有 P0 Bug" + │ "实现用户登录功能" ▼ -┌──────────────────────────────────────────────────────┐ -│ Leader Persona(san start --persona leader --team san-team)│ -│ │ -│ 1. 理解需求 │ -│ 2. 画架构图、状态图(向管理员确认) │ -│ 3. 拆解为 Task,写入共享队列 │ -│ 4. 监控队列,收集完成结果,向管理员汇报 │ -└──────────────────┬───────────────────────────────────┘ - │ 共享工作队列 (state/queue.jsonl) - ┌────────┼────────┐ - ▼ ▼ ▼ - ┌──────────┐ ┌──────┐ ┌─────────┐ - │Dev│ │ QE │ │ Release │ - │ san start│ │san start│ │san start│ - │ --persona│ │--persona│ │--persona│ - │dev│ │ qe │ │ release │ - └──────────┘ └──────┘ └─────────┘ +┌────────────────────────────────────────────┐ +│ Leader(san --persona leader) │ +│ │ +│ 1. 理解需求 │ +│ 2. 画架构图、状态图(向管理员确认) │ +│ 3. 拆解为 Task │ +│ 4. 写入队列:san queue add ... │ +│ 5. 监控:san queue list ... │ +└────────────────┬───────────────────────────┘ + │ 共享工作队列 (state/queue.jsonl) + ┌────────┼────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────┐ ┌─────────┐ + │ run.sh │ │run.sh│ │ run.sh │ + │ dev │ │ qe │ │ release │ + │ │ │ │ │ │ + │ claim→ │ │claim→│ │claim→ │ + │ san -p │ │agent │ │ san -p │ + │ --persona│ │run │ │--persona│ + │ dev │ │--qe │ │ release │ + │ →complete│ │→verify│ │→complete│ + └──────────┘ └──────┘ └─────────┘ ``` ### Leader Persona —— 唯一入口 -Leader 通过 `san start --persona leader --team san-team` 启动, +Leader 通过 `san --persona leader` 启动, 是管理员唯一的交互界面。管理员告诉 Leader 要做什么, Leader 负责: 1. **理解需求**:新功能?Bug 修复?重构? 2. **分析 San 项目**:读取 San 源码仓库中的设计文档和现有代码 3. **可视化**:画 mermaid 架构图、状态图,向管理员确认理解 -4. **拆解**:把功能拆成多个 Task,写入共享工作队列 -5. **监控**:跟踪队列中 Task 的状态变化 +4. **拆解**:把功能拆成多个 Task,通过 `san queue add --dir state/ --role dev --title "..." --description "..."` 写入队列 +5. **监控**:通过 `san queue list --dir state/` 跟踪队列中的 Task 状态 6. **汇报**:收集完成结果,向管理员汇总 Leader 不自己写代码。Leader 把 Task 写入队列后, -由对应 Persona 的 San 实例自动领取执行。 +由对应 Persona 的 `run.sh` 循环自动领取执行。 ``` Leader 派发编码任务示例: Leader: 1. 分析需求后,确定 Task-3 是编码任务 - 2. 将 Task-3 写入队列(标记 role: dev) - 3. Dev San 实例轮询队列,发现 Task-3 匹配自己的角色 - 4. Dev 认领 Task-3,开始实现 - 5. 完成后更新队列状态为 done,附上 PR 链接 - 6. Leader 轮询发现 Task-3 已完成,继续下一步 + 2. 运行:san queue add --dir state/ --role dev --title "实现 JWT..." + 3. Dev 的 run.sh 轮询队列,发现 Task-3 匹配自己的角色 + 4. Dev 通过 san queue claim 认领,运行 san --persona dev -p ".." + 5. Agent 完成,run.sh 调用 san queue complete 附上 PR 链接 + 6. Leader 通过 san queue list 看到 Task-3 已完成 ``` ### Dev Persona —— 编码实现 -通过 `san start --persona dev --team san-team` 启动, -持续轮询队列中的编码任务: +由 `run.sh dev` 启动,持续轮询队列中的编码任务: + +1. `run.sh` 通过 `san queue claim` 认领 role 为 dev 的 Task +2. 从任务描述构建提示词 +3. 运行 `san --persona dev -p "..." +4. Agent 读取 San 项目的设计文档和现有代码 +5. Agent 遵循分层架构规范写代码 +6. Agent 写单元测试(同包内) +7. Agent 跑 `make test` + `make lint` 通过 +8. Agent 提交代码、创建 PR +9. 成功:`run.sh` 调用 `san queue complete --id $ID --pr ` +10. 失败:`run.sh` 调用 `san queue release --id $ID` -1. 从队列认领 role 为 dev 的 Task -2. 读取 San 项目的设计文档和现有代码 -3. 遵循分层架构规范写代码 -4. 写测试 -5. 跑 `make test` + `make lint` 通过 -6. 提交代码、创建 PR -7. 更新队列状态为 done,附上 PR 链接 +Dev Agent 完全不接触队列 —— `run.sh` 根据 Agent 的退出码处理所有队列操作。 ### QE Persona —— 验证测试 -通过 `san start --persona qe --team san-team` 启动, -持续轮询队列中的验证任务: +由 `run.sh qe` 启动,持续轮询队列中的验证任务: -1. 从队列认领 role 为 qe 的 Task(对应 Dev 已完成) -2. 检出 PR 分支 -3. 跑全量测试 + lint + layer check -4. 用 `verify` 技能确认功能正确 -5. 发 PR Review -6. 更新队列状态:通过(verified)或失败(附原因) +1. `run.sh` 认领 role 为 qe 的 Task +2. 运行 `san --persona qe -p "..." +3. Agent 检出 PR 分支 +4. Agent 跑全量测试 + lint + layer check +5. Agent 在 `test-integration/` 中添加集成测试,提交到同一 PR +6. Agent 发 PR Review +7. 通过:`run.sh` 调用 `san queue verify --id $ID --result "passed"` +8. 失败:`run.sh` 调用 `san queue release --id $ID --reason "..."` -也可以在开发前介入:Leader 写好架构图后,先创建验证设计的 Task 给 QE。 +QE 也可以在开发前介入:Leader 写好架构图后,先创建验证设计的 Task 给 QE。 ### Release Persona —— 发布上线 -通过 `san start --persona release --team san-team` 启动: +由 `run.sh release` 启动,持续轮询队列中的发布任务: -1. 从队列认领 role 为 release 的 Task(全部 QE 通过后) -2. 生成 CHANGELOG -3. 更新版本号 -4. 打 Git Tag -5. 生成 Release Notes -6. 更新队列状态为 done +1. `run.sh` 认领 role 为 release 的 Task +2. 运行 `san --persona release -p "..." +3. Agent 生成 CHANGELOG +4. Agent 更新版本号 +5. Agent 打 Git Tag +6. Agent 生成 Release Notes +7. 成功:`run.sh` 调用 `san queue complete --id $ID` ### 共享工作队列 队列是 Persona 之间的唯一通信机制,文件位于 `san-team/state/queue.jsonl`(JSONL 追加日志)。 +所有操作通过 `san queue` 子命令完成(详见 [ADR-0003](0003-shared-work-queue.md))。 ``` type WorkItem struct { @@ -207,15 +293,264 @@ type WorkItem struct { Result string // 结果说明(QE/Release 填充) CreatedAt time.Time UpdatedAt time.Time + ClaimedAt time.Time + RetryCount int + MaxRetries int } ``` 状态流转: ``` -pending ──→ claimed ──→ done ──→ verified - │ │ - └──→ (timeout) ──→ pending(超时未完成,释放回队列) +pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified + ↑ │ │ + └──release()─── ← (超时) ← reject() ──┘ + │ + └──fail()──→ failed +``` + +## 完整安装运行示例 + +从零到团队运行的完整步骤。 + +### 前提条件 + +```bash +# San CLI 已编译且在 PATH 中 +cd /path/to/san +make build +export PATH="$PATH:$PWD/bin" + +# san-team 仓库克隆到 san 同级目录 +cd /path/to +git clone git@github.com:genai-io/san-team.git +``` + +### 第一步:链接 Persona + +```bash +ln -s /path/to/san-team/leader ~/.san/personas/leader +ln -s /path/to/san-team/dev ~/.san/personas/dev +ln -s /path/to/san-team/qe ~/.san/personas/qe +ln -s /path/to/san-team/release ~/.san/personas/release +``` + +### 第二步:启动 Leader(交互式) + +```bash +# 终端 1:Leader 会话 +cd /path/to/san +san --persona leader +``` + +管理员在这里与 Leader 交互。例如: + +``` +管理员:实现用户认证功能,支持 JWT 登录 + +Leader: + 读取 docs/design/ → 分析代码库 → 画时序图 + 与管理员确认 → 拆解为 Task: + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "定义 User 模型和 UserStore 接口" \ + --description "在 internal/core/user.go 中创建 User struct..." + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "基于 SQLite 实现 UserStore" \ + --description "在 internal/feature/userstore/ 中实现 UserStore 接口..." + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "实现 JWT token 生成与验证" \ + --description "创建 internal/core/jwt/ 包..." + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "实现 POST /auth/login handler" \ + --description "创建登录 API handler..." + + $ san queue add --dir /path/to/san-team/state --role qe \ + --title "验证认证模块(Task 1-4)" \ + --description "检出各 PR,运行测试,添加集成测试..." + + $ san queue add --dir /path/to/san-team/state --role release \ + --title "发布 v1.3.0" \ + --description "生成 CHANGELOG,更新版本号,打 tag v1.3.0..." +``` + +### 第三步:启动工作 Persona(无头模式) + +```bash +# 终端 2:Dev 轮询循环 +cd /path/to/san-team +./run.sh dev /path/to/san 30 + +# 终端 3:QE 轮询循环 +cd /path/to/san-team +./run.sh qe /path/to/san 30 + +# 终端 4:Release 轮询循环 +cd /path/to/san-team +./run.sh release /path/to/san 60 +``` + +### 第四步:查看进度 + +```bash +# 任意终端:查看队列状态 +san queue list --dir /path/to/san-team/state +``` + +输出示例: + +``` +ID Role 标题 状态 负责人 PR +a1b2c3d4 dev 定义 User 模型和 UserStore 接口 done dev #123 +b2c3d4e5 dev 实现 UserStore(SQLite) done dev #124 +c3d4e5f6 dev 实现 JWT token 生成 claimed dev - +d4e5f6a7 dev 实现 POST /auth/login pending - - +a7b8c9d0 qe 验证认证模块(Task 1-4) pending - - +b8c9d0e1 release 发布 v1.3.0 pending - - + +Total: 6 | pending: 3 | claimed: 1 | done: 2 | verified: 0 | failed: 0 +``` + +### run.sh 内部运行过程 + +当 Dev 的 `run.sh` 轮询发现一个 pending 任务时: + +``` +1. san queue claim --dir state/ --role dev --persona dev + → 返回 JSON: {"id":"d4e5f6a7...", "title":"实现 POST /auth/login", ...} + → 任务状态变为 "claimed" + +2. san --persona dev -p ".." --prompt "Task: 实现 POST /auth/login + 创建登录 API handler..." --cwd /path/to/san + → Agent 从 ~/.san/personas/dev/ 加载 dev persona + → Agent 实现 handler + 单元测试 + → Agent 创建 PR #126 + → Agent 退出码 0(成功) + +3. san queue complete --dir state/ --id d4e5f6a7 --persona dev \ + --pr "https://github.com/genai-io/san/pull/126" + → 任务状态变为 "done" + +4. sleep 30,重复 +``` + +如果 Agent 执行失败: + +``` +1. san queue claim ... (同上) +2. san --persona dev -p "..." + → Agent 遇到错误,退出码 1(失败) +3. san queue release --dir state/ --id d4e5f6a7 --persona dev + → 任务状态回退 "pending",retryCount++ + → 下次轮询会再次认领(最多重试 maxRetries 次) +``` + +## 完整工作流示例 + +管理员输入:**"实现用户认证功能,支持 JWT 登录"** + +### 1. Leader 理解需求 + 画架构图 + +Leader 读取 San 项目的 `docs/design/`,分析已有代码, +生成 mermaid 时序图: + +```mermaid +sequenceDiagram + participant Client + participant AuthHandler + participant AuthService + participant UserStore + participant JWT + + Client->>AuthHandler: POST /auth/login + AuthHandler->>AuthService: Login(username, password) + AuthService->>UserStore: FindByUsername(username) + UserStore-->>AuthService: User + AuthService->>AuthService: Verify password + AuthService->>JWT: GenerateToken(user) + JWT-->>AuthService: token + AuthService-->>AuthHandler: token + AuthHandler-->>Client: 200 { token } +``` + +Leader 展示图给管理员:"这是我理解的功能流程,对吗?" + +### 2. 拆解 Task 并写入队列 + +管理员确认后,Leader 将 Task 写入共享队列: + +``` +$ san queue add --dir state/ --role dev --title "定义 User 模型和 UserStore 接口" \ + --description "在 internal/core/user.go 中创建 User struct(ID, Username, PasswordHash, CreatedAt)..." + +$ san queue add --dir state/ --role dev --title "基于 SQLite 实现 UserStore" \ + --description "在 internal/feature/userstore/ 中实现 UserStore 接口..." + +$ san queue add --dir state/ --role dev --title "实现 JWT token 生成与验证" \ + --description "创建 internal/core/jwt/ 包..." + +$ san queue add --dir state/ --role dev --title "实现登录 API Handler" \ + --description "在 internal/app/authhandler/ 中创建登录 API handler..." + +$ san queue add --dir state/ --role qe --title "验证全部认证功能" \ + --description "检出 PR,运行测试,添加集成测试..." + +$ san queue add --dir state/ --role release --title "发布 v1.3.0" \ + --description "生成 CHANGELOG,更新版本号,打 tag v1.3.0..." +``` + +### 3. 各 Persona 自动领取执行 + +``` +Dev 的 run.sh 轮询队列: + 认领 Task 1 → san --persona dev -p ".." → 实现 → PR #123 → complete + 认领 Task 2 → san --persona dev -p ".." → 实现 → PR #124 → complete + 认领 Task 3 → san --persona dev -p ".." → 实现 → PR #125 → complete + 认领 Task 4 → san --persona dev -p ".." → 实现 → PR #126 → complete + +QE 的 run.sh 轮询队列: + 发现 Task 1-4 均为 done → 认领 Task 5 + san --persona qe -p ".." → 检出 PR → 运行测试 → 添加集成测试 + → 通过 → san queue verify + +Leader 监控到全部 verified → 通知管理员 approve PR +管理员审批通过 → Leader 运行: + $ san queue add --dir state/ --role release --title "发布 v1.3.0" ... + +Release 的 run.sh 轮询队列: + 认领 Task 6 → san --persona release -p ".." → changelog → tag → complete + +Leader → 管理员: "认证功能已全部完成并发布,PR: #123-#126" +``` + +## Bug 修复流程 + +管理员对 Leader 说:**"扫描并修复所有 P0 Bug"** + +``` +Leader: + 1. 通过 gh 拉取 San 项目所有 P0 Bug issues + 2. 逐个分析,写入队列: + $ san queue add --dir state/ --role dev --title "修复 #100 nil pointer in auth.go" ... + $ san queue add --dir state/ --role dev --title "修复 #102 timeout in db query" ... + +Dev 的 run.sh: + 认领 "#100" → san --persona dev -p ".." → 修复 → PR → complete + 认领 "#102" → san --persona dev -p ".." → 修复 → PR → complete + +QE 的 run.sh: + 认领 "#100 验证" → san --persona qe -p ".." → 验证 → 通过 + 认领 "#102 验证" → san --persona qe -p ".." → 验证 → 通过 + +Leader 通知管理员 approve → 管理员审批通过 + +Release 的 run.sh: + 认领 "hotfix 发布" → changelog → tag → complete + +Leader → 管理员: "2 个 P0 Bug 已全部修复并发布" ``` ## Persona 配置示例 @@ -226,7 +561,7 @@ pending ──→ claimed ──→ done ──→ verified ```markdown 你是 San 项目的开发 Agent。 -你的职责是从共享队列领取编码任务并完成实现。 +你的职责是实现分配给你的编码任务。 你擅长 Go 开发,熟悉 San 的五层包架构。 ``` @@ -235,16 +570,16 @@ pending ──→ claimed ──→ done ──→ verified ```markdown ## 工作方式 -1. 持续轮询共享队列,认领匹配自己角色的 Task -2. 先读取相关的设计文档和现有代码,理解上下文后再动手 -3. 遵循 internal/ 五层架构的依赖方向:cmd → app → feature → core → infrastructure -4. 每个变更必须包含测试 -5. 完成后运行 make test 和 make lint,确保通过后再提交 +1. 先读取相关的设计文档和现有代码,理解上下文后再动手 +2. 遵循 internal/ 五层架构的依赖方向:cmd → app → feature → core → infrastructure +3. 每个变更必须在同包内包含单元测试 +4. 完成后运行 make test 和 make lint,确保通过后再提交 +5. 用清晰的信息提交代码、创建 PR,报告 PR 链接 ## 沟通风格 -- 遇到不确定的设计决策,更新 Task 状态,附上问题等 Leader 回复 -- 完成后在 Task 中附上简洁明确的说明:做了什么、改了哪些文件、PR 链接 +- 遇到不确定的设计决策,在结果中记录问题供 Leader 参考 +- 完成后在结果中附上简洁明确的说明:做了什么、改了哪些文件、PR 链接 - 不要做超出 Task 范围的事 ``` @@ -333,99 +668,6 @@ pending ──→ claimed ──→ done ──→ verified } ``` -## 完整工作流示例 - -管理员输入:**"实现用户认证功能,支持 JWT 登录"** - -### 1. Leader 理解需求 + 画架构图 - -Leader 读取 San 项目的 `docs/design/`,分析已有代码, -生成 mermaid 时序图: - -```mermaid -sequenceDiagram - participant Client - participant AuthHandler - participant AuthService - participant UserStore - participant JWT - - Client->>AuthHandler: POST /auth/login - AuthHandler->>AuthService: Login(username, password) - AuthService->>UserStore: FindByUsername(username) - UserStore-->>AuthService: User - AuthService->>AuthService: Verify password - AuthService->>JWT: GenerateToken(user) - JWT-->>AuthService: token - AuthService-->>AuthHandler: token - AuthHandler-->>Client: 200 { token } -``` - -Leader 展示图给管理员:"这是我理解的功能流程,对吗?" - -### 2. 拆解 Task 并写入队列 - -管理员确认后,Leader 将 Task 写入共享队列: - -``` -队列写入: - Task 1: { role: dev, title: "定义 User 模型和 UserStore 接口" } - Task 2: { role: dev, title: "实现 UserStore" } - Task 3: { role: dev, title: "实现 JWT token 生成与验证" } - Task 4: { role: dev, title: "实现登录 API Handler" } - Task 5: { role: qe, title: "验证全部认证功能" } - Task 6: { role: release, title: "发布 v1.2.0" } -``` - -### 3. 各 Persona 自动领取执行 - -``` -Dev San 实例轮询队列: - 认领 Task 1 → 实现 → 标记 done - 认领 Task 2 → 实现 → 标记 done - 认领 Task 3 → 实现 → 标记 done - 认领 Task 4 → 实现 → 标记 done - -QE San 实例轮询队列: - 发现 Task 1-4 均为 done → 认领 Task 5 - 检出 PR 分支 → 跑测试 → 通过 → 标记 verified - -Leader 监控到全部 verified → 通知管理员 approve PR -管理员审批通过后 → Leader 写入 Task 6 - -Release San 实例轮询队列: - 认领 Task 6 → 生成 CHANGELOG → 打 tag → 标记 done - -Leader → 管理员: "认证功能已全部完成并发布,PR: #1234" -``` - -## Bug 修复流程 - -管理员对 Leader 说:**"扫描并修复所有 P0 Bug"** - -``` -Leader: - 1. 通过 GhCLI 拉取 San 项目所有 P0 Bug issues - 2. 逐个分析,写入队列: - - { role: dev, title: "修复 #100 nil pointer in auth.go" } - - { role: dev, title: "修复 #102 timeout in db query" } - -Dev San 实例: - 认领 "#100" → 分析根因 → 修复 → PR → 标记 done - 认领 "#102" → 分析根因 → 修复 → PR → 标记 done - -QE San 实例: - 认领 "#100 验证" → 测试 → Review PR → 通过 → 标记 verified - 认领 "#102 验证" → 测试 → Review PR → 通过 → 标记 verified - -Leader 通知管理员 approve → 管理员审批通过 - -Release San 实例: - 认领 "hotfix 发布" → 生成 CHANGELOG → 打 tag → 标记 done - -Leader → 管理员: "2 个 P0 Bug 已全部修复并发布" -``` - ## 关键设计决策 ### 1. Persona 目录 @@ -441,31 +683,42 @@ Persona 定义放在独立仓库 `san-team`,与 San 源码仓库分离: - 可以对 san-team 仓库设置不同的访问权限 - 团队 Persona 可以管理多个目标仓库(未来扩展) -### 3. Leader 是唯一入口 +### 3. san-team 是纯内容仓库 —— 无 Go 代码 + +san-team 只包含 markdown 文件、`state/` 和 shell 脚本。所有 Go 逻辑都在 San 中, +作为可复用的独立功能: +- `san queue` —— 原子工作队列操作(详见 [ADR-0003](0003-shared-work-queue.md)) +- `san --persona -p` —— 带 persona 系统提示词的无头 Agent + +这种分离意味着 san-team 没有构建步骤、没有依赖,任何人都可以修改 markdown 和 shell 脚本。 + +### 4. Leader 是唯一入口 管理员不直接跟 Dev/QE/Release 交互,所有指令给 Leader: - 管理员心智模型简单:只有一个对话对象 - Leader 有全局视角,决定优先级和冲突处理 - 其他 Persona 只关注队列中的任务,无需理解全局 -### 4. 每个 Persona 是独立的 San 实例 +### 5. 每个 Persona 是独立的 San 实例 + +每个 Persona 启动一个独立的 San 进程: +- **Leader**:`san --persona leader` — 交互式 TUI,与管理员对话 +- **Dev/QE/Release**:`san --persona -p` — 无头模式,由 `run.sh` 启动 -不是通过 Agent 工具嵌套调用子 Agent,而是每个 Persona 启动一个独立的 -San 进程(`san start --persona --team `): +优点: - 进程级隔离:每个 Persona 有独立的上下文、工具集、权限 - 可以部署在不同机器/容器上,独立扩缩 - 通过共享工作队列(文件)协调,不需要进程间 RPC -- 与 persona 的 `/persona` 切换机制互补: - `/persona` 用于交互式场景中热切换角色,`--persona` 用于启动时就固定角色 +- 所有 persona 加载统一在 `~/.san/personas/` 下 -### 5. 架构图是沟通语言 +### 6. 架构图是沟通语言 Leader 在动手之前先画 mermaid 图: - 管理员确认理解(避免做错方向) - Dev Persona 有清晰的实现参考(图随 Task 描述一起写入队列) - QE Persona 有验证依据(按图检查完整性) -### 6. Persona 自我进化 +### 7. Persona 自我进化 每个 Persona 在项目中持续学习和自我改进。进化过程通过更新 `san-team` 仓库中的 Persona 配置来固化。 @@ -502,11 +755,13 @@ Task 完成 → Persona 写复盘记录 → 发现可改进项 | 新概念 | 对应现有 / 规划中的机制 | |---|---| -| Persona 目录 | Persona 目录(`persona-system.md` 规范) | -| `san start --persona` | 启动时指定 persona,避免启动后热切换 | -| 共享工作队列 | 新增:文件系统 JSONL 队列(`state/queue.jsonl`) | -| Persona 通信 | 通过队列轮询,无需进程间 RPC | -| Persona 权限 | settings.json 中 permissions(deny 只增不减) | +| Persona 目录 | `~/.san/personas/`(现有 persona 系统) | +| `san --persona leader` | 现有 `--persona` 参数(无变化) | +| `san --persona -p` | 现有:`-p` 打印模式配合 `--persona`(PR #231) | +| 共享工作队列 | 新增:`san queue` 子命令(详见 ADR-0003) | +| run.sh 轮询循环 | san-team 中 12 行 shell 脚本 | +| Persona 通信 | 通过 `san queue` 队列轮询,无需进程间 RPC | +| Persona 权限 | settings.json 中 permissions(现有机制) | ## 实现计划 @@ -515,37 +770,38 @@ Task 完成 → Persona 写复盘记录 → 发现可改进项 - 在 `genai-io` Org 下创建 `san-team` 仓库 - 按 persona 规范编写四套 Persona(leader/dev/qe/release) - 每套包含 `system/{identity,behavior,rules}.md` + `skills/` + `settings.json` +- 创建 `run.sh` 轮询脚本 +- 创建 `state/` 目录(queue.jsonl 在运行时创建) -### 阶段二:`san start --persona` 功能 +### 阶段二:添加 `san queue` 子命令 -- San CLI 新增 `--persona` 和 `--team` 参数 -- 启动时加载指定团队下的 Persona 配置 -- 加载 system/ 文件作为系统提示词 -- 加载 skills/ 作为活跃技能 -- 应用 settings.json 中的权限和模型配置 +- 实现 `internal/queue/` 包(item.go, queue.go, claim.go)— ~200 行 +- 接入 `cmd/san/queue.go` cobra 子命令 +- 命令:list, claim, add, complete, verify, release, fail +- 详见 [ADR-0003](0003-shared-work-queue.md) -### 阶段三:共享工作队列 +### 阶段三:`--persona` + `-p` 集成(已完成,PR #231) -- 实现 JSONL 格式的持久化队列(`state/queue.jsonl`) -- 队列写入(Leader 创建 Task) -- 队列轮询(Persona 认领匹配角色的 Task) -- 状态流转(pending → claimed → done → verified) -- 超时释放(claimed 超过 N 分钟未完成,自动回退 pending) +- 修复 `runPrint`,使 `--persona` 和 `-p` 同时提供时加载 persona 系统提示词 +- 从 `~/.san/personas//` 加载 persona,通过 `system.Build()` 构建包含了 + persona identity/behavior/rules 的系统提示词 +- 应用 persona 的 settings.json 中的 model 和 disabledTools +- 这替代了 `run.sh` 需要拼装 persona 文件到 prompt 的需求 -### 阶段四:工作流串联 +### 阶段四:端到端工作流串联 -- Leader 设计文档 → 拆解 Task → 写入队列 -- Dev Persona 轮询 → 领取 → 实现 → 提交 → 更新队列 -- QE Persona 轮询 → 领取 → 验证 → 更新队列 -- Release Persona 轮询 → 领取 → 发布 → 更新队列 -- Leader 监控队列状态 → 向管理员汇报 +- Leader: `san --persona leader` → 理解需求 → 拆解 → `san queue add` +- Dev: `run.sh dev` → `san queue claim` → `san --persona dev -p ".."` → `san queue complete` +- QE: `run.sh qe` → `san queue claim` → `san --persona qe -p ".."` → `san queue verify` +- Release: `run.sh release` → `san queue claim` → `san --persona release -p ".."` → `san queue complete` +- Leader: `san queue list` → 监控 → 向管理员汇报 ### 阶段五:自动触发与运维 - Cron 定时 Bug 扫描 → 自动写入队列 - 设计文档合并后自动触发 Leader 拆解 -- 进度可视化(CLI:`san team status`) -- Persona 实例健康监控 +- 进度可视化(`san queue list`) +- `san queue compact` 用于 JSONL 压缩 ## 参考技能 @@ -599,18 +855,22 @@ Task 完成 → Persona 写复盘记录 → 发现可改进项 2. **Leader 合并权限**:所有代码必须由管理员手动 approve 才能合并,不允许自动 merge。 QE 通过后 Leader 通知管理员来审批,管理员点一下 approve 就行。 3. **失败重试策略**:由 Leader 决定每个 Task 的最大重试次数(默认 3 次)。 - Dev 重试耗尽仍未通过 QE 时,Leader 将 Task 标记为 failed, - 记录失败原因,通知管理员介入。 + Dev 重试耗尽仍未通过 QE 时,`run.sh` 调用 `san queue fail`, + 将 Task 标记为 failed。Leader 检测到后通知管理员介入。 4. **崩溃恢复**:所有异常情况(Persona 实例崩溃、Leader 崩溃、队列文件损坏等) 都必须妥善处理,并向管理员汇报当前状态。包括: - Persona 崩溃:claimed 状态 Task 超时自动回退 pending,Leader 检测到后通知管理员 - Leader 崩溃:管理员重新启动 Leader 后,Leader 回放队列恢复上下文 - 队列文件损坏:从 Git 历史恢复最近一次正常快照 + - `run.sh` 崩溃:直接重启脚本即可,从上次中断处继续(已认领的 Task 超时后自动回退) ## 参考资料 - [`persona-system.md`](../../notes/active/persona-system.md) — Persona 目录遵循的 persona 设计规范 +- [ADR-0003](0003-shared-work-queue.md) — 共享工作队列设计(`san queue`) +- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — prompt-first san-team 设计 +- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — 队列示例 - [`core.Agent`](../../packages/core.md) — 底层 Agent 原语 - [`packages/subagent.md`](../../packages/subagent.md) — 子 Agent 机制 - [`packages/skill.md`](../../packages/skill.md) — 技能加载 diff --git a/docs/cn/design/decisions/0003-shared-work-queue.md b/docs/cn/design/decisions/0003-shared-work-queue.md new file mode 100644 index 00000000..05145f8b --- /dev/null +++ b/docs/cn/design/decisions/0003-shared-work-queue.md @@ -0,0 +1,312 @@ +# ADR-0003:多角色协调共享工作队列 + +## 状态 + +提议 — 2026-06-16。 + +## 背景 + +ADR-0002 定义了一个自主开发管理模型,多个角色(Leader、Dev、QE、Release)以独立的 +San 实例运行,通过共享工作队列进行协调。 + +队列需要满足以下条件: +- 允许多个独立进程安全地领取和更新任务,无需中心服务器或数据库 +- 人类可读、可 Git 版本控制,以支持崩溃恢复 +- 支持任务生命周期状态机:pending → claimed → done → verified +- 可与 shell 脚本(`run.sh` 轮询循环)配合使用,无需在 team 仓库中放置 Go 代码 + +## 决策 + +在 San CLI 中新增 `san queue` 子命令,实现**基于文件的 JSONL 工作队列**。 + +### 与 Persona 系统的关系 + +`san queue` 和 `san --persona -p` 是独立、可组合的功能: + +- **`san queue`** 管理共享工作队列:add、claim、complete、verify、release、fail、 + list。它完全不感知 persona 或 LLM Agent。 +- **`san --persona -p`** 从 `~/.san/personas/` 加载 persona, + 注入其系统提示词(identity + behavior + rules),无头运行 Agent。 + 它完全不知道队列的存在。 + +san-team 中的 `run.sh` 脚本将两者组合在一起: + +``` +run.sh → san queue claim → san --persona -p ".."→ san queue complete/release +``` + +这种分离意味着 `san queue` 可以独立于 san-team 使用 —— 任何多 Agent 编排场景、 +CI 流水线或任务调度器都可以复用。 + +### 存储格式 + +``` +/state/queue.jsonl +``` + +每行是一个紧凑的 JSON 对象。状态变更追加新行(相同 ID,更新字段)。读取时按 ID +去重,保留最后一行。这样队列可以轻松从 Git 历史中恢复(`git checkout -- +state/queue.jsonl`)。 + +### 工作项结构 + +```json +{ + "id": "a1b2c3d4e5f6a7b8", + "role": "dev", + "title": "实现 JWT 令牌生成", + "description": "创建 internal/core/jwt/ 包...", + "status": "claimed", + "assignedTo": "dev", + "pr": "", + "result": "", + "createdAt": "2026-06-16T10:00:00Z", + "updatedAt": "2026-06-16T10:30:00Z", + "claimedAt": "2026-06-16T10:30:00Z", + "retryCount": 0, + "maxRetries": 3 +} +``` + +### 状态机 + +``` +pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified + ↑ │ │ + └──release()─── ← (超时) ← reject() ──┘ + │ + └──fail()──→ failed +``` + +- **claim**:原子"读→找 pending→追加 claimed"操作。通过对 `/queue.lock` 加 + `flock` 防止两个进程领取同一任务。 +- **release**:将 claimed 退回 pending,重试计数 +1。 +- **fail**:当 retryCount >= maxRetries 时,标记为永久失败。 +- **超时释放**:claimed 超过 10 分钟的任务自动退回 pending。 + +### CLI 接口 + +``` +san queue list --dir [--role ] [--status ] +san queue claim --dir --role --persona +san queue add --dir --role --title "..." --description "..." +san queue complete --dir --id --persona [--pr ] [--result "..."] +san queue verify --dir --id --persona [--result "..."] +san queue release --dir --id --persona [--reason "..."] +san queue fail --dir --id --persona [--reason "..."] +``` + +注意:没有 `san queue prompt` 命令。提示词构建由 `run.sh` 完成(从认领的任务 JSON +中提取 `title` 和 `description`)。Persona 系统提示词注入由 `san --persona -p` +处理。 + +### 包结构 + +``` +internal/queue/ ← feature 层 + item.go WorkItem、ItemStatus、状态转换、ID 生成 + queue.go JSONL 存储,基于 flock 的原子追加 + claim.go Claimer:claim、release、complete、verify、fail 及超时释放 +cmd/san/queue.go cobra 子命令注册 +``` + +## 完整示例:认证功能生命周期 + +以下展示在一个真实功能实现过程中,队列项如何流经状态机。同样的示例保存在 +[`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md)。 + +### 1. Leader 创建任务(全部以 pending 开始) + +Leader 拆解"实现用户认证功能"并运行: + +```bash +san queue add --dir state/ --role dev \ + --title "定义 User 模型和 UserStore 接口" \ + --description "在 internal/core/user.go 中创建 User struct(ID, Username, PasswordHash, CreatedAt)。定义 UserStore 接口,包含 FindByUsername, Create, FindByID。" + +san queue add --dir state/ --role dev \ + --title "实现 UserStore(SQLite)" \ + --description "在 internal/feature/userstore/ 中基于 SQLite 实现 UserStore 接口。参数化查询,bcrypt 密码哈希。" + +san queue add --dir state/ --role dev \ + --title "实现 JWT token 生成与验证" \ + --description "创建 internal/core/jwt/ 包。GenerateToken 和 ValidateToken,HS256,24 小时过期。" + +san queue add --dir state/ --role dev \ + --title "实现 POST /auth/login handler" \ + --description "在 internal/app/authhandler/ 中创建登录 API handler。接收 {username, password},返回 {token}。" + +san queue add --dir state/ --role dev \ + --title "实现 Auth 中间件" \ + --description "创建中间件:提取 Bearer token、验证、注入用户信息到 context。" + +san queue add --dir state/ --role dev \ + --title "登录接口加限流" \ + --description "限制 POST /auth/login 每 IP 每分钟 5 次。" + +san queue add --dir state/ --role qe \ + --title "验证认证模块" \ + --description "检出所有认证相关 PR。运行 make test、make lint、make layercheck。在 test-integration/auth/ 下添加集成测试。" + +san queue add --dir state/ --role release \ + --title "发布 v1.3.0" \ + --description "生成 CHANGELOG,版本号升至 1.3.0,打 tag v1.3.0,生成 release notes。" +``` + +Leader 写入后的队列: + +``` +ID Role 标题 状态 负责人 +a1b2c3d4 dev 定义 User 模型和接口 pending - +b2c3d4e5 dev 实现 UserStore(SQLite) pending - +c3d4e5f6 dev 实现 JWT token 生成 pending - +d4e5f6a7 dev 实现 POST /auth/login pending - +e5f6a7b8 dev 实现 Auth 中间件 pending - +f6a7b8c9 dev 登录接口加限流 pending - +a7b8c9d0 qe 验证认证模块 pending - +b8c9d0e1 release 发布 v1.3.0 pending - +``` + +### 2. Dev 认领并实现 + +Dev 的 `run.sh` 执行:`san queue claim --dir state/ --role dev --persona dev` + +认领是原子的(flock 保护)。同一时间只有一个进程能认领同一任务。 + +``` +认领 Task 1 → status: claimed, assignedTo: dev + san --persona dev -p ".." → 实现 → PR #123 + san queue complete --id a1b2c3d4 --pr "https://github.com/genai-io/san/pull/123" + → status: done + +认领 Task 2 → status: claimed, assignedTo: dev + san --persona dev -p ".." → 实现 → PR #124 + san queue complete --id b2c3d4e5 --pr "https://github.com/genai-io/san/pull/124" + → status: done + +... (Task 3-6 同理) +``` + +### 3. QE 验证 + +QE 的 `run.sh` 轮询 `role: qe` 的任务。当所有 dev 任务 done 后: + +``` +$ san queue claim --dir state/ --role qe --persona qe +→ 认领 Task a7b8c9d0(验证认证模块),status: claimed + +$ san --persona qe -p "验证认证模块..." +→ 检出 PR #123-#126 → 运行测试 → 添加集成测试 → 通过 + +$ san queue verify --id a7b8c9d0 --persona qe --result "全部测试通过,已添加集成测试。" +→ status: verified +``` + +### 4. Release 发布 + +Release 的 `run.sh` 轮询 `role: release` 的任务: + +``` +$ san queue claim --dir state/ --role release --persona release +→ 认领 Task b8c9d0e1(发布 v1.3.0),status: claimed + +$ san --persona release -p "发布 v1.3.0..." +→ 生成 CHANGELOG → 更新版本号 → git tag v1.3.0 + +$ san queue complete --id b8c9d0e1 --persona release +→ status: done +``` + +### 5. 失败重试示例 + +``` +Task f6a7b8c9: "修复 auth 中间件的 nil pointer" + 第 1 次:claimed → agent 失败 → release → pending(retryCount: 1) + 第 2 次:claimed → agent 失败 → release → pending(retryCount: 2) + 第 3 次:claimed → agent 失败 → release → pending(retryCount: 3) + retryCount >= maxRetries → san queue fail → status: failed + +Leader 检测到失败任务,通知管理员介入。 +``` + +### 完成时的队列全景 + +```bash +$ san queue list --dir state/ +``` + +``` +ID Role 标题 状态 负责人 PR +a1b2c3d4 dev 定义 User 模型和接口 done dev #123 +b2c3d4e5 dev 实现 UserStore(SQLite) done dev #124 +c3d4e5f6 dev 实现 JWT token 生成 done dev #125 +d4e5f6a7 dev 实现 POST /auth/login done dev #126 +e5f6a7b8 dev 实现 Auth 中间件 done dev #127 +f6a7b8c9 dev 登录接口加限流 done dev #128 +a7b8c9d0 qe 验证认证模块 verified qe - +b8c9d0e1 release 发布 v1.3.0 done release - + +Total: 8 | pending: 0 | claimed: 0 | done: 7 | verified: 1 | failed: 0 +``` + +### 与 run.sh 的交互 + +完整的 `run.sh` 循环,展示队列和 Agent 如何组合: + +```bash +#!/bin/bash +set -euo pipefail +TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" +PERSONA="${1:?usage: $0 }" +CWD="${2:-$(pwd)}" +INTERVAL="${3:-30}" + +echo "[san-team:$PERSONA] 每 ${INTERVAL}s 轮询一次, cwd=$CWD" + +while true; do + TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) + if [ -n "$TASK" ]; then + ID=$(echo "$TASK" | jq -r '.id') + TITLE=$(echo "$TASK" | jq -r '.title') + DESC=$(echo "$TASK" | jq -r '.description') + echo "[san-team:$PERSONA] 认领 $ID: $TITLE" + + PROMPT="Task: $TITLE + + $DESC + + 完成后,总结你的工作内容和 PR 链接。" + + if san --persona "$PERSONA" -p "$PROMPT"; then + san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] 完成 $ID" + else + san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] 释放 $ID(将重试)" + fi + fi + sleep "$INTERVAL" +done +``` + +关键设计要点:`run.sh` 是唯一同时接触队列和 Agent 的组件。队列不知道 persona +的存在。Agent 不知道队列的存在。Shell 脚本是组合层。 + +## 影响 + +- **正面**:无数据库依赖。队列是单个 JSONL 文件,可以 `cat` 查看、`grep` 搜索、 + Git 恢复。 +- **正面**:`san queue` 独立于 san-team 可用。任何多 Agent 编排场景都可以复用。 +- **正面**:CLI 接口可脚本化——`san-team` 中的轮询循环只需 12 行 shell 脚本。 +- **正面**:清晰的职责分离。`san queue` 处理原子队列操作。`san --persona -p` + 处理带 persona 的 Agent 执行。`run.sh` 负责组合它们。 +- **负面**:JSONL 追加模式会无限增长。后续应增加 `san queue compact` 命令来压 + 缩(每个 ID 只保留最后一行)。 +- **负面**:基于文件的锁意味着所有队列操作必须在同一文件系统上。跨机器协调需要 + 共享文件系统(NFS)或通过 SSH 使用 shell 回退方案。 + +## 参考资料 + +- [ADR-0002](0002-autonomous-dev-management.md) — 自主开发管理团队 +- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — Prompt 优先的 san-team 设计 +- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — 队列示例 diff --git a/docs/design/decisions/0002-autonomous-dev-management.md b/docs/design/decisions/0002-autonomous-dev-management.md index 5d440359..693910a8 100644 --- a/docs/design/decisions/0002-autonomous-dev-management.md +++ b/docs/design/decisions/0002-autonomous-dev-management.md @@ -2,7 +2,7 @@ ## Status -Proposed — 2026-06-11. +Proposed — 2026-06-11. Updated 2026-06-16. ## Context @@ -28,14 +28,13 @@ Each Persona follows the ``` genai-io(Org, existing) ├── san ← San source repo (existing, the managed project) -└── san-team ← New repo (San development team Personas) +└── san-team ← New repo (pure content, no Go code) ├── leader/ │ ├── system/ │ │ ├── identity.md │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ - │ │ └── ... │ └── settings.json ├── dev/ │ ├── system/ @@ -43,7 +42,6 @@ genai-io(Org, existing) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ - │ │ └── ... │ └── settings.json ├── qe/ │ ├── system/ @@ -51,22 +49,21 @@ genai-io(Org, existing) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ - │ │ └── ... │ └── settings.json - └── release/ - ├── system/ - │ ├── identity.md - │ ├── behavior.md - │ └── rules.md - ├── skills/ - │ └── ... - └── settings.json -``` - -- **`san-team`**: New repo containing the San project's development team. - Personas live directly under the repo root. If other projects need - similar autonomous management, they can create their own team repos - (e.g., `devops-team`). + ├── release/ + │ ├── system/ + │ │ ├── identity.md + │ │ ├── behavior.md + │ │ └── rules.md + │ ├── skills/ + │ └── settings.json + ├── state/ ← queue.jsonl lives here + └── run.sh ← polling loop (12-line shell script) +``` + +- **`san-team`**: A pure-content repo — no Go code, no binaries. Contains + only persona markdown files, a shared state directory, and a shell script + for the polling loop. All Go logic lives in San as reusable features. - **Team**: A collection of Personas in the `san-team` repo that collaborate toward a specific goal — managing the San project's issues, features, bugs, and releases. @@ -79,129 +76,225 @@ genai-io(Org, existing) ## Runtime Model -Each Persona runs as an **independent San instance**: +Each Persona runs as an **independent San instance**. The runtime model +differs by persona role: + +### Leader — Interactive TUI + +Leader is the admin's conversation partner. Runs as a standard interactive +San session with the leader persona loaded: ```bash -# Start Leader Persona (admin interaction entry point) -san start --persona leader --team san-team +san --persona leader +``` -# Start Dev Persona (waits for coding tasks) -san start --persona dev --team san-team +This uses San's existing `--persona` flag, which loads persona config +from `~/.san/personas/leader/` (a symlink to `san-team/leader/`). -# Start QE Persona (waits for verification tasks) -san start --persona qe --team san-team +### Dev, QE, Release — Headless via run.sh + +These personas run headlessly via `san --persona -p`, launched by +`run.sh`. They do not need interactive terminals — they poll the queue, +claim tasks, execute them, and update the queue: + +```bash +# In terminal 2: start Dev polling loop +./run.sh dev /path/to/san -# Start Release Persona (waits for release tasks) -san start --persona release --team san-team +# In terminal 3: start QE polling loop +./run.sh qe /path/to/san + +# In terminal 4: start Release polling loop +./run.sh release /path/to/san +``` + +`run.sh` is a simple shell script (see below). Each persona's system prompt +(identity + behavior + rules) is loaded by `san --persona -p` from +`~/.san/personas//`. + +### Setup: Linking Personas + +Before starting, link san-team personas into San's persona directory: + +```bash +ln -s /path/to/san-team/leader ~/.san/personas/leader +ln -s /path/to/san-team/dev ~/.san/personas/dev +ln -s /path/to/san-team/qe ~/.san/personas/qe +ln -s /path/to/san-team/release ~/.san/personas/release ``` -The `--persona` flag tells San to load a specific Persona directory's -config (system/ + skills/ + settings.json) at startup, avoiding the -need for mid-session `/persona` switching. +All personas — Leader, Dev, QE, Release — use the same `~/.san/personas/` +mechanism. No new persona-loading code is needed. -Multiple Persona instances can run simultaneously in different -terminals, containers, or machines. They coordinate through a shared -work queue (`san-team/state/queue.jsonl`). +## run.sh — The Glue Layer + +`run.sh` is the only "code" in san-team. It bridges `san queue` and +`san -p`: + +```bash +#!/bin/bash +set -euo pipefail +TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" +PERSONA="${1:?usage: $0 }" +CWD="${2:-$(pwd)}" +INTERVAL="${3:-30}" + +echo "[san-team:$PERSONA] polling every ${INTERVAL}s, cwd=$CWD" + +while true; do + # 1. Atomically claim next pending task for this persona + TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) + if [ -n "$TASK" ]; then + ID=$(echo "$TASK" | jq -r '.id') + TITLE=$(echo "$TASK" | jq -r '.title') + DESC=$(echo "$TASK" | jq -r '.description') + echo "[san-team:$PERSONA] claimed $ID: $TITLE" + + # 2. Build task prompt and run agent headlessly + PROMPT="Task: $TITLE + + $DESC + + After completing, summarize what you did and any PR links." + + if san --persona "$PERSONA" -p "$PROMPT"; then + san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] completed $ID" + else + san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] released $ID (will retry)" + fi + fi + sleep "$INTERVAL" +done +``` + +Key design points: +- **Queue operations are the shell's job**: `run.sh` calls `san queue claim`, + `san queue complete`, `san queue release`. The LLM agent never knows about + the queue. +- **Persona loading is San's job**: `san --persona -p` loads the + persona system prompt from `~/.san/personas/`. The shell does not need to + compose persona files into the prompt. +- **LLM inference is San's job**: `san -p` executes the agent with + the task description as the user message. +- **The shell is just glue**: claim → run → complete/release. 12 lines. ## Workflow The admin talks only to the Leader Persona. Leader breaks requirements -into Tasks and writes them to the shared queue. Other Personas poll the -queue, claim matching tasks, complete them, and update the queue status. +into Tasks and writes them to the shared queue via `san queue add`. +Other Personas poll the queue via `run.sh`, claim matching tasks, +complete them, and update the queue status. ``` Admin (human) │ - │ "Build user authentication" or "Fix all P0 bugs" + │ "Build user authentication" ▼ -┌──────────────────────────────────────────────────────┐ -│ Leader Persona (san start --persona leader --team san-team)│ -│ │ -│ 1. Understand intent │ -│ 2. Draw architecture & state diagrams │ -│ 3. Break down into Tasks, write to shared queue │ -│ 4. Monitor queue, collect results, report to admin │ -└──────────────────┬───────────────────────────────────┘ - │ Shared work queue (state/queue.jsonl) - ┌────────┼────────┐ - ▼ ▼ ▼ - ┌──────────┐ ┌──────┐ ┌─────────┐ - │Dev│ │ QE │ │ Release │ - │ san start│ │san start│ │san start│ - │ --persona│ │--persona│ │--persona│ - │dev│ │ qe │ │ release │ - └──────────┘ └──────┘ └─────────┘ +┌────────────────────────────────────────────┐ +│ Leader (san --persona leader) │ +│ │ +│ 1. Understand intent │ +│ 2. Draw architecture & state diagrams │ +│ 3. Break down into Tasks │ +│ 4. Write tasks via: san queue add ... │ +│ 5. Monitor: san queue list ... │ +└────────────────┬───────────────────────────┘ + │ Shared work queue (state/queue.jsonl) + ┌────────┼────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────┐ ┌─────────┐ + │ run.sh │ │run.sh│ │ run.sh │ + │ dev │ │ qe │ │ release │ + │ │ │ │ │ │ + │ claim→ │ │claim→│ │claim→ │ + │ san -p │ │agent │ │ san -p │ + │ --persona│ │run │ │--persona│ + │ dev │ │--qe │ │ release │ + │ →complete│ │→verify│ │→complete│ + └──────────┘ └──────┘ └─────────┘ ``` ### Leader Persona — Single Entry Point -Started via `san start --persona leader --team san-team`. -The admin's only interface. Leader handles: +Started via `san --persona leader`. The admin's only interface. Leader +handles: 1. **Understand intent**: new feature? bug fix? refactor? 2. **Analyze San**: read design docs and existing code in the San repo 3. **Visualize**: draw mermaid architecture/state diagrams, confirm with admin -4. **Break down**: decompose into Tasks, write to shared work queue -5. **Monitor**: track Task status changes in the queue +4. **Break down**: decompose into Tasks, write to shared work queue via + `san queue add --dir state/ --role dev --title "..." --description "..."` +5. **Monitor**: track Task status via `san queue list --dir state/` 6. **Report**: collect results, summarize for the admin Leader does not write code. It writes Tasks to the queue; the matching -Persona's San instance picks them up automatically. +Persona's `run.sh` loop picks them up automatically. ``` Leader dispatches a coding task: Leader: 1. After analysis, determines Task-3 is a coding task - 2. Writes Task-3 to queue (marked role: dev) - 3. Dev San instance polls queue, finds Task-3 matching its role - 4. Dev claims Task-3, starts implementation - 5. On completion, updates queue status to done with PR link - 6. Leader polls queue, sees Task-3 done, proceeds to next step + 2. Runs: san queue add --dir state/ --role dev --title "Implement JWT..." + 3. Dev's run.sh polls queue, finds Task-3 matching its role + 4. Dev claims Task-3 via san queue claim, runs san --persona dev -p ".." + 5. Agent completes, run.sh calls san queue complete with PR link + 6. Leader polls queue via san queue list, sees Task-3 done ``` ### Dev Persona — Implementation -Started via `san start --persona dev --team san-team`. -Continuously polls the queue for coding tasks: +Launched by `run.sh dev`. Continuously polls the queue for coding tasks: + +1. `run.sh` claims Tasks from queue with role `dev` via `san queue claim` +2. Builds prompt from task description +3. Runs `san --persona dev -p "..." +4. Agent reads San's design docs and existing code +5. Agent implements following the layered architecture conventions +6. Agent writes unit tests (in the same package) +7. Agent runs `make test` + `make lint` +8. Agent commits, creates PR +9. On success: `run.sh` calls `san queue complete --id $ID --pr ` +10. On failure: `run.sh` calls `san queue release --id $ID` -1. Claims Tasks from queue with role `dev` -2. Reads San's design docs and existing code -3. Implements following the layered architecture conventions -4. Writes tests -5. Runs `make test` + `make lint` -6. Commits, creates PR -7. Updates queue status to `done` with PR link +The Dev agent never touches the queue — `run.sh` handles all queue +operations based on the agent's exit code. ### QE Persona — Verification -Started via `san start --persona qe --team san-team`. -Continuously polls the queue for verification tasks: +Launched by `run.sh qe`. Continuously polls the queue for verification tasks: -1. Claims Tasks from queue with role `qe` (corresponding to Dev done) -2. Checks out the PR branch -3. Runs full test suite + lint + layer check -4. Uses `verify` skill to confirm correctness -5. Posts PR review -6. Updates queue status: `verified` or failed with reason +1. `run.sh` claims Tasks from queue with role `qe` +2. Runs `san --persona qe -p "..." +3. Agent checks out the PR branch +4. Agent runs full test suite + lint + layer check +5. Agent adds integration tests in `test-integration/`, commits to same PR +6. Agent posts PR review +7. On pass: `run.sh` calls `san queue verify --id $ID --result "passed"` +8. On fail: `run.sh` calls `san queue release --id $ID --reason "..."` -Can also verify designs before implementation starts. +QE can also verify designs before implementation starts (Leader creates +a `role: qe` task with the design doc to review). ### Release Persona — Shipping -Started via `san start --persona release --team san-team`: +Launched by `run.sh release`. Continuously polls the queue for release tasks: -1. Claims Tasks from queue with role `release` (after all QE verified) -2. Generates CHANGELOG -3. Bumps version number -4. Creates Git tag -5. Generates release notes -6. Updates queue status to `done` +1. `run.sh` claims Tasks from queue with role `release` +2. Runs `san --persona release -p "..." +3. Agent generates CHANGELOG +4. Agent bumps version number +5. Agent creates Git tag +6. Agent generates release notes +7. On success: `run.sh` calls `san queue complete --id $ID` ### Shared Work Queue The queue is the sole communication mechanism between Personas, stored -at `san-team/state/queue.jsonl` (JSONL append-only log). +at `san-team/state/queue.jsonl` (JSONL append-only log). All operations +go through the `san queue` subcommand (see [ADR-0003](0003-shared-work-queue.md)). ``` type WorkItem struct { @@ -215,15 +308,263 @@ type WorkItem struct { Result string // result notes (filled by QE/Release) CreatedAt time.Time UpdatedAt time.Time + ClaimedAt time.Time + RetryCount int + MaxRetries int } ``` State transitions: ``` -pending ──→ claimed ──→ done ──→ verified - │ │ - └──→ (timeout) ──→ pending (timed out, released back to queue) +pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified + ↑ │ │ + └──release()─── ← (timeout) ← reject() ──┘ + │ + └──fail()──→ failed +``` + +## Complete Setup Example + +A step-by-step walkthrough from zero to running team. + +### Prerequisites + +```bash +# San CLI must be built and on PATH +cd /path/to/san +make build +export PATH="$PATH:$PWD/bin" + +# san-team repo cloned alongside san +cd /path/to +git clone git@github.com:genai-io/san-team.git +``` + +### Step 1: Link personas + +```bash +ln -s /path/to/san-team/leader ~/.san/personas/leader +ln -s /path/to/san-team/dev ~/.san/personas/dev +ln -s /path/to/san-team/qe ~/.san/personas/qe +ln -s /path/to/san-team/release ~/.san/personas/release +``` + +### Step 2: Start Leader (interactive) + +```bash +# Terminal 1: Leader session +cd /path/to/san +san --persona leader +``` + +The admin interacts with Leader here. For example: + +``` +Admin: Build user authentication with JWT login + +Leader: + Reads docs/design/ → analyzes codebase → draws sequence diagram + Confirms with admin → breaks down into tasks: + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "Define User model and UserStore interface" \ + --description "Create User struct (ID, Username, PasswordHash, CreatedAt)..." + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "Implement UserStore with SQLite" \ + --description "Implement UserStore interface using SQLite..." + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "Implement JWT token generation and verification" \ + --description "Create internal/core/jwt/ package..." + + $ san queue add --dir /path/to/san-team/state --role dev \ + --title "Implement POST /auth/login handler" \ + --description "Create login API handler..." + + $ san queue add --dir /path/to/san-team/state --role qe \ + --title "Verify auth module (tasks 1-4)" \ + --description "Check out each PR, run tests, add integration tests..." + + $ san queue add --dir /path/to/san-team/state --role release \ + --title "Ship v1.3.0 with user authentication" \ + --description "Generate CHANGELOG, bump version, tag v1.3.0..." +``` + +### Step 3: Start worker personas (headless) + +```bash +# Terminal 2: Dev polling loop +cd /path/to/san-team +./run.sh dev /path/to/san 30 + +# Terminal 3: QE polling loop +cd /path/to/san-team +./run.sh qe /path/to/san 30 + +# Terminal 4: Release polling loop +cd /path/to/san-team +./run.sh release /path/to/san 60 +``` + +### Step 4: Watch progress + +```bash +# Any terminal: check queue status +san queue list --dir /path/to/san-team/state +``` + +Output example: + +``` +ID Role Title Status Assigned PR +a1b2c3d4 dev Define User model and interface done dev #123 +b2c3d4e5 dev Implement UserStore with SQLite done dev #124 +c3d4e5f6 dev Implement JWT token generation claimed dev - +d4e5f6a7 dev Implement POST /auth/login pending - - +a7b8c9d0 qe Verify auth module (tasks 1-4) pending - - +b8c9d0e1 release Ship v1.3.0 pending - - + +Total: 6 | pending: 3 | claimed: 1 | done: 2 | verified: 0 | failed: 0 +``` + +### What happens inside run.sh + +When Dev's `run.sh` polls and finds a pending task: + +``` +1. san queue claim --dir state/ --role dev --persona dev + → Returns JSON: {"id":"d4e5f6a7...", "title":"Implement POST /auth/login", ...} + → Task status is now "claimed" + +2. san --persona dev -p ".." --prompt "Task: Implement POST /auth/login + Create login API handler at internal/app/authhandler/..." --cwd /path/to/san + → Agent loads dev persona from ~/.san/personas/dev/ + → Agent implements the handler + unit tests + → Agent creates PR #126 + → Agent exits 0 (success) + +3. san queue complete --dir state/ --id d4e5f6a7 --persona dev --pr "https://github.com/genai-io/san/pull/126" + → Task status is now "done" + +4. sleep 30, repeat +``` + +If the agent fails: + +``` +1. san queue claim ... (same) +2. san --persona dev -p "..." + → Agent hits an error, exits 1 (failure) +3. san queue release --dir state/ --id d4e5f6a7 --persona dev + → Task status reverts to "pending", retryCount++ + → Next poll will claim it again (up to maxRetries) +``` + +## Complete Workflow Example + +Admin input: **"Implement user authentication with JWT login"** + +### 1. Leader understands + draws diagrams + +Leader reads San's `docs/design/`, analyzes existing code, generates a +mermaid sequence diagram: + +```mermaid +sequenceDiagram + participant Client + participant AuthHandler + participant AuthService + participant UserStore + participant JWT + + Client->>AuthHandler: POST /auth/login + AuthHandler->>AuthService: Login(username, password) + AuthService->>UserStore: FindByUsername(username) + UserStore-->>AuthService: User + AuthService->>AuthService: Verify password + AuthService->>JWT: GenerateToken(user) + JWT-->>AuthService: token + AuthService-->>AuthHandler: token + AuthHandler-->>Client: 200 { token } +``` + +Leader shows the diagram: "This is how I understand the flow — correct?" + +### 2. Break down and write to queue + +After admin confirmation, Leader writes Tasks to the shared queue: + +``` +$ san queue add --dir state/ --role dev --title "Define User model and UserStore interface" \ + --description "Create User struct (ID, Username, PasswordHash, CreatedAt) in internal/core/user.go..." + +$ san queue add --dir state/ --role dev --title "Implement UserStore with SQLite" \ + --description "Implement UserStore interface using SQLite..." + +$ san queue add --dir state/ --role dev --title "Implement JWT token generation & verification" \ + --description "Create internal/core/jwt/ package..." + +$ san queue add --dir state/ --role dev --title "Implement login API handler" \ + --description "Create login API handler at internal/app/authhandler/..." + +$ san queue add --dir state/ --role qe --title "Verify full auth functionality" \ + --description "Check out PRs, run tests, add integration tests..." + +$ san queue add --dir state/ --role release --title "Ship v1.3.0" \ + --description "Generate CHANGELOG, bump version, tag v1.3.0..." +``` + +### 3. Personas claim and execute + +``` +Dev's run.sh polls queue: + Claims Task 1 → san --persona dev -p ".." → implements → PR #123 → complete + Claims Task 2 → san --persona dev -p ".." → implements → PR #124 → complete + Claims Task 3 → san --persona dev -p ".." → implements → PR #125 → complete + Claims Task 4 → san --persona dev -p ".." → implements → PR #126 → complete + +QE's run.sh polls queue: + Sees Tasks 1-4 done → claims Task 5 + san --persona qe -p ".." → checks out PRs → runs tests → adds integration tests + → passes → san queue verify + +Leader monitors all verified → notifies admin to approve PRs +Admin approves → Leader runs: + $ san queue add --dir state/ --role release --title "Ship v1.3.0" ... + +Release's run.sh polls queue: + Claims Task 6 → san --persona release -p ".." → changelog → tag → complete + +Leader → Admin: "Authentication feature complete and shipped. PRs: #123-#126" +``` + +## Bug Fix Flow + +Admin tells Leader: **"Scan and fix all P0 bugs"** + +``` +Leader: + 1. Pulls all P0 bug issues from San repo via gh + 2. Analyzes each, writes to queue: + $ san queue add --dir state/ --role dev --title "Fix #100 nil pointer in auth.go" ... + $ san queue add --dir state/ --role dev --title "Fix #102 timeout in db query" ... + +Dev's run.sh: + Claims "#100" → san --persona dev -p ".." → fix → PR → complete + Claims "#102" → san --persona dev -p ".." → fix → PR → complete + +QE's run.sh: + Claims "#100 verify" → san --persona qe -p ".." → verify → passes + Claims "#102 verify" → san --persona qe -p ".." → verify → passes + +Leader notifies admin to approve → admin approves + +Release's run.sh: + Claims "hotfix release" → changelog → tag → complete + +Leader → Admin: "2 P0 bugs fixed and shipped" ``` ## Persona Configuration Examples @@ -234,7 +575,7 @@ Each Persona is a persona directory. Using Dev as an example: ```markdown You are the Dev agent for the San project. -Your job is to claim coding tasks from the shared queue and implement them. +Your job is to implement coding tasks assigned to you. You are an expert Go developer familiar with San's five-layer package architecture. ``` @@ -243,16 +584,16 @@ You are an expert Go developer familiar with San's five-layer package architectu ```markdown ## Work habits -1. Continuously poll the shared queue for tasks matching your role -2. Read relevant design docs and existing code first — understand context before acting -3. Follow the 5-layer architecture dependency direction: cmd → app → feature → core → infrastructure -4. Every change must include tests -5. Run make test and make lint after changes — only proceed if they pass +1. Read relevant design docs and existing code first — understand context before acting +2. Follow the 5-layer architecture dependency direction: cmd → app → feature → core → infrastructure +3. Every change must include unit tests in the same package +4. Run make test and make lint after changes — only proceed if they pass +5. Commit with clear messages, create a PR, and report the PR link ## Communication style -- When uncertain about a design decision, update the Task with your question for the Leader -- On completion, update the Task with what you did, which files changed, and the PR link +- When uncertain about a design decision, note it in your result for the Leader +- On completion, summarize what you did, which files changed, and the PR link - Do not go beyond the assigned Task scope ``` @@ -343,99 +684,6 @@ and decision-making. } ``` -## Complete Workflow Example - -Admin input: **"Implement user authentication with JWT login"** - -### 1. Leader understands + draws diagrams - -Leader reads San's `docs/design/`, analyzes existing code, generates a -mermaid sequence diagram: - -```mermaid -sequenceDiagram - participant Client - participant AuthHandler - participant AuthService - participant UserStore - participant JWT - - Client->>AuthHandler: POST /auth/login - AuthHandler->>AuthService: Login(username, password) - AuthService->>UserStore: FindByUsername(username) - UserStore-->>AuthService: User - AuthService->>AuthService: Verify password - AuthService->>JWT: GenerateToken(user) - JWT-->>AuthService: token - AuthService-->>AuthHandler: token - AuthHandler-->>Client: 200 { token } -``` - -Leader shows the diagram: "This is how I understand the flow — correct?" - -### 2. Break down and write to queue - -After admin confirmation, Leader writes Tasks to the shared queue: - -``` -Queue writes: - Task 1: { role: dev, title: "Define User model and UserStore interface" } - Task 2: { role: dev, title: "Implement UserStore" } - Task 3: { role: dev, title: "Implement JWT token generation & verification" } - Task 4: { role: dev, title: "Implement login API handler" } - Task 5: { role: qe, title: "Verify full auth functionality" } - Task 6: { role: release, title: "Ship v1.2.0" } -``` - -### 3. Personas claim and execute - -``` -Dev San instance polls queue: - Claims Task 1 → implements → marks done - Claims Task 2 → implements → marks done - Claims Task 3 → implements → marks done - Claims Task 4 → implements → marks done - -QE San instance polls queue: - Sees Tasks 1-4 done → claims Task 5 - Checks out PR branch → runs tests → passes → marks verified - -Leader monitors all verified → notifies admin to approve PR -Admin approves → Leader writes Task 6 - -Release San instance polls queue: - Claims Task 6 → generates CHANGELOG → tags → marks done - -Leader → Admin: "Authentication feature complete and shipped. PR: #1234" -``` - -## Bug Fix Flow - -Admin tells Leader: **"Scan and fix all P0 bugs"** - -``` -Leader: - 1. Pulls all P0 bug issues from San repo via GhCLI - 2. Analyzes each, writes to queue: - - { role: dev, title: "Fix #100 nil pointer in auth.go" } - - { role: dev, title: "Fix #102 timeout in db query" } - -Dev San instance: - Claims "#100" → analyzes root cause → fix → PR → marks done - Claims "#102" → analyzes root cause → fix → PR → marks done - -QE San instance: - Claims "#100 verify" → tests → reviews PR → passes → marks verified - Claims "#102 verify" → tests → reviews PR → passes → marks verified - -Leader notifies admin to approve → admin approves - -Release San instance: - Claims "hotfix release" → generates CHANGELOG → tags → marks done - -Leader → Admin: "2 P0 bugs fixed and shipped" -``` - ## Key Design Decisions ### 1. Persona directory @@ -452,31 +700,44 @@ Persona definitions live in a dedicated repo, separate from the San source: - Different access permissions for the san-team repo - Team Personas can manage multiple target repos (future) -### 3. Leader is the single entry point +### 3. san-team is pure content — no Go code + +san-team contains only markdown files, `state/`, and a shell script. All Go +logic lives in San as reusable, independently useful features: +- `san queue` — atomic work queue operations (see [ADR-0003](0003-shared-work-queue.md)) +- `san --persona -p` — headless agent with persona system prompt + +This separation means san-team has no build step, no dependencies, and can +be modified by anyone comfortable with markdown and shell scripts. + +### 4. Leader is the single entry point The admin never talks directly to Dev/QE/Release: - Simple mental model: one conversation partner - Leader has global visibility to prioritize and handle conflicts - Other Personas focus only on their queue tasks, don't need global context -### 4. Each Persona is an independent San instance +### 5. Each Persona is an independent San instance Instead of nested sub-agent calls via the Agent tool, each Persona runs -as an independent San process (`san start --persona --team `): +as an independent San process: +- **Leader**: `san --persona leader` — interactive TUI, talks to admin +- **Dev/QE/Release**: `san --persona -p` — headless, launched by `run.sh` + +Benefits: - Process-level isolation: each Persona has its own context, tools, permissions - Can be deployed on different machines/containers, independently scaled - Coordination through the shared work queue (file-based), no IPC needed -- Complements the `/persona` switch mechanism: `/persona` for interactive - mid-session hot-switching; `--persona` for fixed-role startup +- All persona loading unified under `~/.san/personas/` -### 5. Architecture diagrams as communication language +### 6. Architecture diagrams as communication language Leader draws mermaid diagrams before writing any code: - Admin confirms understanding (avoids building the wrong thing) - Dev Persona gets a clear reference (diagrams ship with Task descriptions) - QE Persona gets a verification checklist -### 6. Persona Self-Evolution +### 7. Persona Self-Evolution Every Persona continuously learns and self-improves during the project. Evolution is persisted by updating the Persona's configuration in the @@ -514,11 +775,13 @@ Task complete → Persona writes retrospective → identifies improvements | New Concept | Existing / Planned Mechanism | |---|---| -| Persona directory | Persona directory (`persona-system.md` spec) | -| `san start --persona` | Startup-time persona selection (no mid-session switch needed) | -| Shared work queue | New: filesystem JSONL queue (`state/queue.jsonl`) | -| Persona communication | Queue polling, no inter-process RPC needed | -| Persona permissions | settings.json permissions (deny: add-only) | +| Persona directory | `~/.san/personas/` (existing persona system) | +| `san --persona leader` | Existing `--persona` flag (no change) | +| `san --persona -p` | Existing: `-p` print mode with `--persona` (PR #231) | +| Shared work queue | New: `san queue` subcommand (see ADR-0003) | +| run.sh polling loop | 12-line shell script in san-team | +| Persona communication | Queue polling via `san queue`, no inter-process RPC | +| Persona permissions | settings.json permissions (existing mechanism) | ## Implementation Plan @@ -527,37 +790,39 @@ Task complete → Persona writes retrospective → identifies improvements - Create `san-team` repo under `genai-io` Org - Write four Persona directories (leader/dev/qe/release) - Each with `system/{identity,behavior,rules}.md` + `skills/` + `settings.json` +- Create `run.sh` polling script +- Create `state/` directory (queue.jsonl created at runtime) -### Phase 2 — `san start --persona` feature +### Phase 2 — Add `san queue` subcommand to San -- San CLI adds `--persona` and `--team` flags -- On startup, load the specified Persona config from the team directory -- Load system/ files as the system prompt -- Load skills/ as active skills -- Apply settings.json permissions and model config +- Implement `internal/queue/` package (item, queue, claim) — ~200 lines +- Wire `cmd/san/queue.go` cobra subcommand +- Commands: list, claim, add, complete, verify, release, fail +- See [ADR-0003](0003-shared-work-queue.md) for details -### Phase 3 — Shared work queue +### Phase 3 — `--persona` + `-p` integration (done, PR #231) -- Implement JSONL persistent queue (`state/queue.jsonl`) -- Queue write (Leader creates Tasks) -- Queue poll (Personas claim tasks matching their role) -- State transitions (pending → claimed → done → verified) -- Timeout release (claimed tasks auto-revert to pending after timeout) +- Fix `runPrint` to load persona system prompt when `--persona` and `-p` are + combined +- Load persona from `~/.san/personas//` and build system prompt via + `system.Build()` with persona identity/behavior/rules +- Apply persona's settings.json model and disabledTools +- This replaces the need for `run.sh` to compose persona files into the prompt ### Phase 4 — End-to-end workflows -- Leader: design doc → break down → write to queue -- Dev Persona: poll → claim → implement → commit → update queue -- QE Persona: poll → claim → verify → update queue -- Release Persona: poll → claim → ship → update queue -- Leader: monitor queue status → report to admin +- Leader: `san --persona leader` → understand → break down → `san queue add` +- Dev: `run.sh dev` → `san queue claim` → `san --persona dev -p ".."` → `san queue complete` +- QE: `run.sh qe` → `san queue claim` → `san --persona qe -p ".."` → `san queue verify` +- Release: `run.sh release` → `san queue claim` → `san --persona release -p ".."` → `san queue complete` +- Leader: `san queue list` → monitor → report to admin ### Phase 5 — Automation and operations - Cron-triggered bug scanning → auto-write to queue - Auto-trigger Leader task breakdown on design doc merge -- Progress dashboard (CLI: `san team status`) -- Persona instance health monitoring +- Progress dashboard (`san queue list`) +- `san queue compact` for JSONL compaction ## Reference Skills @@ -616,8 +881,9 @@ directly adopt them or use them as design references. before merging — no automatic merge. After QE passes, Leader notifies the admin to approve; the admin just clicks approve. 3. **Failure retry strategy**: The Leader decides the max retry count per Task - (default 3). When Dev exhausts retries and still fails QE, Leader - marks the Task as `failed`, records the reason, and notifies the admin. + (default 3). When Dev exhausts retries and still fails QE, `run.sh` calls + `san queue fail`, marking the Task as `failed`. Leader detects this and + notifies the admin. 4. **Crash recovery**: All failure scenarios (Persona crash, Leader crash, queue file corruption, etc.) must be handled gracefully and reported to the admin: @@ -625,12 +891,17 @@ directly adopt them or use them as design references. detects the reversion and notifies the admin - Leader crash: admin restarts Leader, which replays the queue to restore context - Queue file corruption: recover the last good snapshot from Git history + - `run.sh` crash: simply restart the script; it picks up where it left off + (any claimed task auto-reverts after timeout) 5. **Future team repos**: Beyond `san-team`, what infrastructure will teams like `devops-team` need? ## References - [`persona-system.md`](../../notes/active/persona-system.md) — persona design spec that Persona directories follow +- [ADR-0003](0003-shared-work-queue.md) — shared work queue design (`san queue`) +- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — prompt-first san-team design +- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — queue examples - [`core.Agent`](../../packages/core.md) — underlying agent primitive - [`packages/subagent.md`](../../packages/subagent.md) — sub-agent mechanism - [`packages/skill.md`](../../packages/skill.md) — skill loading diff --git a/docs/design/decisions/0003-shared-work-queue.md b/docs/design/decisions/0003-shared-work-queue.md new file mode 100644 index 00000000..2a3d6ce4 --- /dev/null +++ b/docs/design/decisions/0003-shared-work-queue.md @@ -0,0 +1,325 @@ +# ADR-0003: Shared Work Queue for Multi-Persona Coordination + +## Status + +Proposed — 2026-06-16. + +## Context + +ADR-0002 defines an autonomous development management model where multiple +personas (Leader, Dev, QE, Release) run as independent San instances and +coordinate through a shared work queue. + +The queue must: +- Allow multiple independent processes to safely claim and update tasks + without a central server or database +- Be human-readable and git-versionable for crash recovery +- Support a task lifecycle state machine: pending → claimed → done → verified +- Work with shell scripts (`run.sh` polling loop) without requiring a Go binary + in the team repo + +## Decision + +Implement a **file-based JSONL work queue** served by a new `san queue` +subcommand in the San CLI. + +### Relationship to Persona System + +`san queue` and `san --persona -p` are independent, composable features: + +- **`san queue`** manages the shared work queue: add, claim, complete, verify, + release, fail, list. It knows nothing about personas or LLM agents. +- **`san --persona -p`** loads a persona from `~/.san/personas/`, + injects its system prompt (identity + behavior + rules), and runs a headless + agent. It knows nothing about the queue. + +The `run.sh` script in san-team composes them: + +``` +run.sh → san queue claim → san --persona -p ".."→ san queue complete/release +``` + +This separation means `san queue` is independently useful beyond san-team — +any multi-agent orchestration scenario, CI pipeline, or task scheduler can +use it. + +### Storage format + +``` +/state/queue.jsonl +``` + +Each line is a compact JSON object. Status changes append new lines (same ID, +updated fields). Readers deduplicate by ID, keeping the latest line. This +makes the queue trivially recoverable from Git history (`git checkout -- +state/queue.jsonl`). + +### Work item schema + +```json +{ + "id": "a1b2c3d4e5f6a7b8", + "role": "dev", + "title": "Implement JWT token generation", + "description": "Create internal/core/jwt/ package...", + "status": "claimed", + "assignedTo": "dev", + "pr": "", + "result": "", + "createdAt": "2026-06-16T10:00:00Z", + "updatedAt": "2026-06-16T10:30:00Z", + "claimedAt": "2026-06-16T10:30:00Z", + "retryCount": 0, + "maxRetries": 3 +} +``` + +### State machine + +``` +pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified + ↑ │ │ + └──release()─── ← (timeout) ← reject() ──┘ + │ + └──fail()──→ failed +``` + +- `claim`: atomic read→find pending→append claimed operation. Uses `flock` on + `/queue.lock` to prevent two processes claiming the same item. +- `release`: reverts claimed→pending, bumps retryCount. +- `fail`: when retryCount >= maxRetries, mark as permanently failed. +- Timeout release: claimed items older than 10 minutes auto-revert to pending. + +### CLI surface + +``` +san queue list --dir [--role ] [--status ] +san queue claim --dir --role --persona +san queue add --dir --role --title "..." --description "..." +san queue complete --dir --id --persona [--pr ] [--result "..."] +san queue verify --dir --id --persona [--result "..."] +san queue release --dir --id --persona [--reason "..."] +san queue fail --dir --id --persona [--reason "..."] +``` + +Note: there is no `san queue prompt` command. Prompt composition is done by +`run.sh` (extracting `title` and `description` from the claimed task JSON). +Persona system prompt injection is handled by `san --persona -p`. + +### Package structure + +``` +internal/queue/ ← feature layer + item.go WorkItem, ItemStatus, state transitions, ID generation + queue.go JSONLStore with flock-based atomic append + claim.go Claimer: claim, release, complete, verify, fail + stale release +cmd/san/queue.go Cobra subcommand wiring +``` + +## Complete Example: Authentication Feature Lifecycle + +This shows how queue items flow through the state machine during a real +feature implementation. The same example lives at +[`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md). + +### 1. Leader creates tasks (all start as pending) + +Leader breaks down "Build user authentication" and runs: + +```bash +san queue add --dir state/ --role dev \ + --title "Define User model and UserStore interface" \ + --description "Create User struct (ID, Username, PasswordHash, CreatedAt) in internal/core/user.go. Define UserStore interface with FindByUsername, Create, FindByID methods." + +san queue add --dir state/ --role dev \ + --title "Implement UserStore with SQLite" \ + --description "Implement UserStore interface using SQLite in internal/feature/userstore/. Parameterized queries, bcrypt password hashing." + +san queue add --dir state/ --role dev \ + --title "Implement JWT token generation and verification" \ + --description "Create internal/core/jwt/ package with GenerateToken and ValidateToken. HS256, 24h expiry." + +san queue add --dir state/ --role dev \ + --title "Implement POST /auth/login handler" \ + --description "Create login API handler at internal/app/authhandler/. Accept {username, password}, return {token}." + +san queue add --dir state/ --role dev \ + --title "Implement auth middleware" \ + --description "Create middleware that extracts Bearer token, validates, injects user info into context." + +san queue add --dir state/ --role dev \ + --title "Add rate limiting to login endpoint" \ + --description "Limit POST /auth/login to 5 attempts per minute per IP." + +san queue add --dir state/ --role qe \ + --title "Verify auth module" \ + --description "Check out all auth PRs. Run make test, make lint, make layercheck. Add integration tests under test-integration/auth/." + +san queue add --dir state/ --role release \ + --title "Ship v1.3.0 with user authentication" \ + --description "Generate CHANGELOG, bump version to 1.3.0, tag v1.3.0, generate release notes." +``` + +Queue after Leader's work: + +``` +ID Role Title Status Assigned +a1b2c3d4 dev Define User model and interface pending - +b2c3d4e5 dev Implement UserStore with SQLite pending - +c3d4e5f6 dev Implement JWT token generation pending - +d4e5f6a7 dev Implement POST /auth/login pending - +e5f6a7b8 dev Implement auth middleware pending - +f6a7b8c9 dev Add rate limiting to login pending - +a7b8c9d0 qe Verify auth module pending - +b8c9d0e1 release Ship v1.3.0 pending - +``` + +### 2. Dev claims and implements + +Dev's `run.sh` runs: `san queue claim --dir state/ --role dev --persona dev` + +Claim is atomic (flock-protected). Only one process can claim a given item. + +``` +Claim Task 1 → status: claimed, assignedTo: dev + san --persona dev -p ".." → implements → PR #123 + san queue complete --id a1b2c3d4 --pr "https://github.com/genai-io/san/pull/123" + → status: done + +Claim Task 2 → status: claimed, assignedTo: dev + san --persona dev -p ".." → implements → PR #124 + san queue complete --id b2c3d4e5 --pr "https://github.com/genai-io/san/pull/124" + → status: done + +... (Tasks 3-6 similarly) +``` + +### 3. QE verifies + +QE's `run.sh` polls for `role: qe` tasks. When all dev tasks are done: + +``` +$ san queue claim --dir state/ --role qe --persona qe +→ Claims Task a7b8c9d0 (Verify auth module), status: claimed + +$ san --persona qe -p "Verify auth module..." +→ Checks out PRs #123-#126 → runs tests → adds integration tests → passes + +$ san queue verify --id a7b8c9d0 --persona qe --result "All tests pass. Integration tests added." +→ status: verified +``` + +### 4. Release ships + +Release's `run.sh` polls for `role: release` tasks: + +``` +$ san queue claim --dir state/ --role release --persona release +→ Claims Task b8c9d0e1 (Ship v1.3.0), status: claimed + +$ san --persona release -p "Ship v1.3.0..." +→ Generates CHANGELOG → bumps version → git tag v1.3.0 + +$ san queue complete --id b8c9d0e1 --persona release +→ status: done +``` + +### 5. Failure and retry example + +``` +Task f6a7b8c9: "Fix nil pointer in auth middleware" + Attempt 1: claimed → agent fails → release → pending (retryCount: 1) + Attempt 2: claimed → agent fails → release → pending (retryCount: 2) + Attempt 3: claimed → agent fails → release → pending (retryCount: 3) + retryCount >= maxRetries → san queue fail → status: failed + +Leader detects failed task and notifies admin. +``` + +### Full queue state at completion + +```bash +$ san queue list --dir state/ +``` + +``` +ID Role Title Status Assigned PR +a1b2c3d4 dev Define User model and interface done dev #123 +b2c3d4e5 dev Implement UserStore with SQLite done dev #124 +c3d4e5f6 dev Implement JWT token generation done dev #125 +d4e5f6a7 dev Implement POST /auth/login done dev #126 +e5f6a7b8 dev Implement auth middleware done dev #127 +f6a7b8c9 dev Add rate limiting to login done dev #128 +a7b8c9d0 qe Verify auth module verified qe - +b8c9d0e1 release Ship v1.3.0 done release - + +Total: 8 | pending: 0 | claimed: 0 | done: 7 | verified: 1 | failed: 0 +``` + +### Interaction with run.sh + +The complete `run.sh` loop showing how queue and agent compose: + +```bash +#!/bin/bash +set -euo pipefail +TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" +PERSONA="${1:?usage: $0 }" +CWD="${2:-$(pwd)}" +INTERVAL="${3:-30}" + +echo "[san-team:$PERSONA] polling every ${INTERVAL}s, cwd=$CWD" + +while true; do + TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) + if [ -n "$TASK" ]; then + ID=$(echo "$TASK" | jq -r '.id') + TITLE=$(echo "$TASK" | jq -r '.title') + DESC=$(echo "$TASK" | jq -r '.description') + echo "[san-team:$PERSONA] claimed $ID: $TITLE" + + PROMPT="Task: $TITLE + + $DESC + + After completing, summarize what you did and any PR links." + + if san --persona "$PERSONA" -p "$PROMPT"; then + san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] completed $ID" + else + san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" + echo "[san-team:$PERSONA] released $ID (will retry)" + fi + fi + sleep "$INTERVAL" +done +``` + +Key design insight: `run.sh` is the only component that touches both the +queue and the agent. The queue doesn't know about personas. The agent +doesn't know about the queue. The shell script is the composition layer. + +## Consequences + +- **Positive**: No database dependency. The queue is a single JSONL file that + can be inspected with `cat`, searched with `grep`, and recovered from Git. +- **Positive**: `san queue` is independently useful beyond san-team. Any + multi-agent orchestration scenario can use it. +- **Positive**: The CLI surface is scriptable — the polling loop in `san-team` + is a 12-line shell script. +- **Positive**: Clean separation of concerns. `san queue` handles atomic queue + operations. `san --persona -p` handles persona-aware agent execution. + `run.sh` composes them. +- **Negative**: JSONL append-only grows unboundedly. Periodic compaction (keep + only the latest line per ID) should be added as a `san queue compact` command + later. +- **Negative**: File-based locking means all queue operations must be on the + same filesystem. Cross-machine coordination requires a shared filesystem (NFS) + or the shell fallback over SSH. + +## References + +- [ADR-0002](0002-autonomous-dev-management.md) — autonomous development management team +- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — prompt-first san-team design +- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — queue examples From 3419a913ccb81f7c85805059d0a62c10165871aa Mon Sep 17 00:00:00 2001 From: dangpeng Date: Wed, 17 Jun 2026 21:13:59 +0800 Subject: [PATCH 2/4] docs(design): add default read-only persona ADR Introduce a built-in readonly persona with minimal prompts (~80% token savings vs defaults), full write-operation blocking via permissions.deny, and two-layer defense (enforcement + advisory). Chinese translation included. Co-Authored-By: Claude Opus 4.7 Signed-off-by: dangpeng --- .../0004-default-readonly-persona.md | 332 ++++++++++++++++ .../0004-default-readonly-persona.md | 366 ++++++++++++++++++ 2 files changed, 698 insertions(+) create mode 100644 docs/cn/design/decisions/0004-default-readonly-persona.md create mode 100644 docs/design/decisions/0004-default-readonly-persona.md diff --git a/docs/cn/design/decisions/0004-default-readonly-persona.md b/docs/cn/design/decisions/0004-default-readonly-persona.md new file mode 100644 index 00000000..5628e22a --- /dev/null +++ b/docs/cn/design/decisions/0004-default-readonly-persona.md @@ -0,0 +1,332 @@ +# ADR-0004: 默认只读 Persona + +## 状态 + +提案中 — 2026-06-17. + +## 背景 + +San 目前没有默认 Persona。当没有明确选择 Persona 时,系统提示词由内置默认部分拼装, +所有工具均可用 —— 包括文件编辑、Git 提交、Shell 命令等写操作。 + +这意味着一个全新的 San 会话没有任何只读保护。常见的初始工作流 —— +"解释这段代码"、"这个测试为什么失败"、"这个包是做什么的" —— 本不需要写权限, +但用户却得到了全部权限。 + +目标是提供一个内置的只读 Persona,具有两个核心优势: + +1. **环境保护**:Persona 在物理上无法修改用户的工作环境 —— + 不能写文件、不能变更 Git 状态、不能安装包。这消除了意外或幻觉产生的破坏性操作 + 损坏项目状态的风险。 + +2. **节省 Token**:默认系统提示词携带了大量与只读工作无关的内容 —— + 工程方法论、提交规范、Git 协议、任务管理规则、针对破坏性操作的安全约束。 + 只读 Persona 用最简化的、针对性的 prompt 替换全部三个散文部分, + 删除所有不服务于阅读、分析和解释的文本。 + +## 核心模型 + +一个**内置于 San 二进制文件中的 "readonly" Persona**。与早期草案中回退到 +San 默认 `behavior.md` 不同,此 Persona **覆盖全部三个散文部分**, +每个部分都采用最简化、针对只读场景的内容。San 默认的工程提示词中 +没有任何内容残留 —— 模型只看到只读助手所需的内容。 + +``` +内嵌于二进制文件: + readonly/ + ├── system/ + │ ├── identity.md ← 最简:"你是一个只读助手" + │ ├── behavior.md ← 最简:如何分析、解释、调试 + │ └── rules.md ← 最简:只读约束 + 被要求写操作时的回应方式 + └── settings.json ← permissions.deny 阻止写工具(强制执行层) +``` + +每个部分都刻意精简。设计原则:**如果一句话无助于模型阅读、分析或解释,就删掉。** + +### Prompt 内容 + +`system/identity.md`: + +```markdown +你是一个只读助手。你回答问题、分析代码、调试环境。 +你绝不修改文件、代码、配置或系统状态。 +``` + +`system/behavior.md`: + +```markdown +## 分析 + +分析代码时:先通读相关文件,系统性地追踪逻辑,在给出修复建议前找准根因。 +解释时:清晰简洁。使用具体的引用(文件路径、行号、函数名)。 + +## 调试 + +调试时:检查日志、检查状态、追踪执行路径。 +运行只读诊断命令(ls、cat、grep、git log、git status、git diff)。 +先定位问题,再解释问题。 + +## 沟通 + +- 回答所问的问题 —— 不跑题。 +- 直接回答优于铺陈叙述。 +- 如需更多上下文,追问而非猜测。 +``` + +`system/rules.md`: + +```markdown +## 只读约束 + +你当前处于只读模式。以下操作被禁止: + +- 创建、修改或删除文件 +- 执行会写入文件系统的 Shell 命令 +- 改变仓库状态的 Git 操作(commit、push、merge、rebase、tag 等) +- 安装包、依赖或系统软件 +- 修改任何配置 + +## 你可以做什么 + +- 读取文件和搜索代码库 +- 回答关于代码、架构、设计和规范的问题 +- 分析 Bug、追踪执行路径、解释行为 +- 运行只读 Shell 命令:ls、cat、grep、find、git log、git status、 + git diff、git show、git blame 等 +- 调试环境和诊断问题 + +## 被要求写操作时 + +说明你处于只读模式,无法执行写操作。 +建议通过 `/persona ` 切换到有写权限的 Persona。 +``` + +### 为什么覆盖 behavior.md + +San 的默认 `behavior.md` 包含了约 30 行工程方法论:工作习惯(先读设计文档、 +遵循分层架构、编写单元测试、运行 lint)、沟通风格(报告改了哪些文件、关联 PR)、 +范围控制。这些对只读会话完全无用。用最简化的替代内容覆盖它可以回收这些 Token, +并给模型提供真正与阅读分析相关的指导。 + +### 为什么覆盖 rules.md + +San 的默认 `rules.md` 集成了安全策略、工具协议、任务/Git 规范、破坏性命令的安全规则、 +以及各 Provider 的特殊行为。对只读会话而言这些都是无效负载 —— +Persona 根本没有写能力,关于提交信息格式、PR 规范、`--no-verify` 的规则纯属噪音。 +最简化的替代内容仅保留只读约束。 + +### Token 节省估算 + +San 内置默认值与只读替代内容的行数对比: + +| 部分 | 默认(约行数) | 只读(约行数) | 节省 | +|---|---|---|---| +| `identity` | ~3 | ~2 | 较少 | +| `behavior` | ~30 | ~12 | ~60% | +| `rules` | ~120 | ~18 | ~85% | +| **合计** | **~153** | **~32** | **~80%** | + +这些节省对每轮包含系统提示词的推理都生效(包括缓存未命中时)。 +同时,因为 Persona 提示词属于 prompt-cache 前缀的一部分,更小的提示词也意味着更小的缓存条目。 + +### 允许的只读操作 + +| 类别 | 示例 | +|---|---| +| 读取文件 | `Read`、`cat`、`head`、`tail` | +| 搜索 | `grep`、`find`、`git grep` | +| Git 读取 | `git log`、`git status`、`git diff`、`git show`、`git blame` | +| 分析 | 代码审查、架构分析、Bug 追踪 | +| 问答 | 关于代码、设计、规范的问题 | +| 调试 | 追踪错误、检查日志、检查环境状态 | + +### 禁止的写操作 + +| 类别 | 示例 | +|---|---| +| 文件写入 | `Edit`、`Write`、Shell 重定向(`>`、`>>`)、`tee` | +| 文件删除 | `rm`、`rmdir`、`shred` | +| 文件移动/复制 | `mv`、`cp`、`dd` | +| 文件创建 | `touch`、`mkdir` | +| 权限修改 | `chmod`、`chown` | +| Git 写入 | `commit`、`push`、`merge`、`rebase`、`tag`、`am`、`cherry-pick`、`stash` | +| 包安装 | `go install`、`npm install`、`pip install`、`make install`、`brew install` | +| 破坏性操作 | `rm -rf`、force push、`git reset --hard` | + +## 决策 + +### D1:内置于二进制文件,而非磁盘文件 + +Readonly Persona 通过 `embed.FS` 编译进 San 二进制文件,而非从 +`~/.san/personas/` 或 `.san/personas/` 扫描。即使全新安装、没有任何 +Persona 目录,该 Persona 也始终可用。 + +它仍然注册在 Persona 注册表中,因此 `/persona readonly` 可以正常切换, +并在 `/persona` 选择器中可见。 + +### D2:暂不作为系统默认值 + +Readonly Persona **默认可用**,但未选择 Persona 时的系统默认行为暂时不变 —— +保持全权限访问。 + +用户通过 `/persona readonly` 或在设置中配置 `persona: readonly` 来主动选择。 +是否将其设为系统默认值,留待评估迁移影响后再决定。 + +**理由**:改变默认 Persona 会打破用户对 San 会话启动即有全工具访问的预期。 +这需要充分的社区沟通,而不仅仅是代码变更。先交付 Persona 本身,待社区达成共识后再调整默认值。 + +### D3:覆盖全部三个散文部分 —— 不回退到默认值 + +Persona 提供自己的 `identity.md`、`behavior.md` 和 `rules.md`。 +不使用任何 San 的默认散文部分。这是有意为之: + +- **Token 效率**:默认部分承载了大量工程工作流的内容。去掉它们可节省系统提示词中 + 约 80% 的散文(见上文估算)。 +- **信号清晰**:模型只收到与只读任务相关的指令。没有工程方法论、Git 协议或提交规范 + 稀释提示词。 +- **无死规则**:关于 `--no-verify`、提交信息格式、分支命名、PR 描述的规则 + 在 Persona 无法写入的情况下纯属噪音。 + +每个部分保持最简 —— 设计目标是能产生正确只读行为的最短提示词。 + +### D4:双层防御 —— permissions.deny(强制)+ rules.md(建议) + +与 Persona 模型的设计哲学一致,采用双层防御: + +1. **`settings.json` 的 `permissions.deny`** —— *强制执行*层。在权限引擎层 + 阻止写工具调用,模型根本收不到这些工具。根据 Persona 权限合并语义, + 该 deny 列表不能被更低层(用户/项目设置)放松。 + +2. **`system/rules.md`** —— *建议*层。模型每轮阅读的自然语言约束。 + 即使某个工具漏过了 deny 列表,模型也被引导远离写操作。 + +```json +{ + "description": "只读 Persona — 回答问题、分析代码、调试环境。不可写入。", + "skills": {}, + "agents": [], + "disabledTools": {}, + "permissions": { + "defaultMode": "default", + "deny": [ + "Edit", + "Write", + "Bash(rm:*)", + "Bash(rmdir:*)", + "Bash(mv:*)", + "Bash(cp:*)", + "Bash(touch:*)", + "Bash(mkdir:*)", + "Bash(dd:*)", + "Bash(shred:*)", + "Bash(chmod:*)", + "Bash(chown:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(git merge:*)", + "Bash(git tag:*)", + "Bash(git rebase:*)", + "Bash(git reset:*)", + "Bash(git am:*)", + "Bash(git cherry-pick:*)", + "Bash(git stash:*)", + "Bash(go install:*)", + "Bash(npm install:*)", + "Bash(yarn add:*)", + "Bash(pip install:*)", + "Bash(pip3 install:*)", + "Bash(brew install:*)", + "Bash(make install:*)", + "Bash(curl * | *)", + "Bash(wget -O:*)" + ] + } +} +``` + +### D5:Git hooks —— 已评估,暂缓实施 + +Git hooks 曾被考虑作为第三层防御,但决定暂缓: + +- **优点**:pre-commit hook 可以在 Git 层面阻止提交,独立于 San 的工具权限体系, + 实现纵深防御。 +- **缺点**:hooks 仅覆盖 Git 操作,不覆盖一般文件写入。权限引擎已经覆盖了全部工具面。 + hooks 需要按仓库单独安装。活跃 Persona 是 San 运行时概念 —— hooks 没有原生的查询方式。 + +**后续如需**:提供一个 `san persona current` CLI 命令来输出当前活跃 Persona。 +pre-commit hook 可以调用它: + +```bash +#!/bin/bash +if [ "$(san persona current 2>/dev/null)" = "readonly" ]; then + echo "只读 Persona 模式下禁止提交。" + exit 1 +fi +``` + +这属于未来扩展,不在本 ADR 实施范围内。 + +### D6:子 Agent 继承 + +当 Readonly Persona 通过 Agent 工具创建子 Agent 时,只读约束通过权限层传播 —— +子 Agent 使用相同的 effective settings overlay。无需特殊的子 Agent 处理逻辑。 + +## 实现计划 + +### 阶段一:内嵌 Persona 文件 + +创建 `internal/persona/builtin/readonly/`: + +``` +internal/persona/builtin/readonly/ +├── system/ +│ ├── identity.md +│ ├── behavior.md +│ └── rules.md +└── settings.json +``` + +通过 `embed.FS` 内嵌。在 Persona 注册表中注册为内置条目,不可被同名磁盘 Persona 覆盖。 + +### 阶段二:`/persona` 集成 + +- 在 `/persona` 选择器中展示 "readonly" 选项。 +- 支持 `/persona readonly` 切换。 +- 持久化选择,使 `persona: readonly` 配置在重启后仍然生效。 + +### 阶段三:将 Prompt 接入 Persona 加载器 + +按上文 [Prompt 内容](#prompt-内容) 定义的三个文件(`identity.md`、`behavior.md`、 +`rules.md`)编写并与 `settings.json` 一并内嵌。 + +## 影响 + +### 正面 + +- **环境保护**:工作目录免受意外修改。无文件损坏、无意外的 Git 变更、 + 无幻觉导致的破坏性命令。 +- **Token 节省(约 80%)**:Persona 将约 153 行默认系统提示词散文替换为约 32 行 + 只读专用内容。更小的提示词在每轮推理中节省 Token,同时减小 prompt-cache 体积。 +- **信号清晰**:模型只收到与阅读、分析和解释相关的指令 —— + 没有工程方法论或 Git 协议稀释提示词。 +- **安全的起点**:用户可以安全地探索、提问和分析代码,无需担心意外写入。 +- **显式提权**:写操作需要主动切换 Persona,使用户意图明确清晰。 +- **无需新增基础设施**:复用现有 Persona 系统、权限引擎和 settings overlay, + 零新增机制。 + +### 负面 / 代价 + +- **Deny 列表覆盖度**:deny 列表必须显式枚举所有写工具和命令模式。 + 未来添加的新的可写工具(或不符合 deny 模式的创造性 Shell 命令)可能漏过。 + 建议层的 `rules.md` 可以缓解但不能根除这个缺陷。 +- **非沙箱**:San Persona 运行在用户的信任级别上 —— 这是护栏,不是安全边界。 + 有决心的用户或插件可以绕过。 +- **用户困惑**:期望开箱就能编辑文件的用户切换到 readonly 模式后会被阻止。 + Persona 选择器必须清晰展示每个 Persona 的能力边界。 + +## 参考资料 + +- [`persona.md`](../../concepts/persona.md) — Persona 系统设计 +- [`permission-model.md`](../../concepts/permission-model.md) — 权限引擎 +- [`ADR-0001`](0001-layered-package-architecture.md) — 分层包架构 +- [`ADR-0002`](0002-autonomous-dev-management.md) — 自主开发团队(Persona 配置示例) diff --git a/docs/design/decisions/0004-default-readonly-persona.md b/docs/design/decisions/0004-default-readonly-persona.md new file mode 100644 index 00000000..39b3f7aa --- /dev/null +++ b/docs/design/decisions/0004-default-readonly-persona.md @@ -0,0 +1,366 @@ +# ADR-0004: Default Read-Only Persona + +## Status + +Proposed — 2026-06-17. + +## Context + +San ships without a default persona. When no persona is selected, the system +prompt is assembled from built-in defaults and all tools are available — +including write operations like file editing, git commits, and shell commands. + +This means a fresh San session has no read-only guardrails. Common starting +workflows — "explain this code," "why is this test failing," "what does this +package do" — don't need write access, but the user gets it anyway. + +The goal is to ship a built-in readonly persona with two core benefits: + +1. **Environment protection**: The persona physically cannot modify the user's + working environment — no file writes, no git mutations, no package installs. + This eliminates the risk of an accidental or hallucinated destructive + operation corrupting the project state. + +2. **Token savings**: The default system prompt carries substantial weight from + parts that are irrelevant to read-only work — engineering methodology, + commit conventions, git protocols, task management rules, and safety + constraints for destructive operations. A read-only persona replaces all + three prose parts with minimal, purpose-specific prompts, dropping every + word that does not serve reading, analyzing, or explaining. + +## Core Model + +A **built-in "readonly" persona** embedded in the San binary. Unlike the +earlier draft that fell back to San's default `behavior.md`, this persona +**overrides all three prose parts** with minimal, read-only-specific content. +Nothing from San's default engineering prompt survives — the model only sees +what a read-only assistant needs. + +``` +embedded in binary: + readonly/ + ├── system/ + │ ├── identity.md ← minimal: "You are a read-only assistant" + │ ├── behavior.md ← minimal: how to analyze, explain, debug + │ └── rules.md ← minimal: read-only constraint + what to do when asked to write + └── settings.json ← permissions.deny for write tools (enforcement layer) +``` + +Each part is intentionally short. The design principle is: **if a sentence +does not help the model read, analyze, or explain, cut it.** + +### Prompt content + +`system/identity.md`: + +```markdown +You are a read-only assistant. You answer questions, analyze code, and debug +environments. You never modify files, code, config, or system state. +``` + +`system/behavior.md`: + +```markdown +## Analysis + +When analyzing code: read the relevant files first, trace the logic +systematically, identify root causes before suggesting fixes. When +explaining: be clear and concise. Use concrete references (file paths, +line numbers, function names). + +## Debugging + +When debugging: check logs, inspect state, trace execution paths. Run +read-only diagnostic commands (ls, cat, grep, git log, git status, +git diff). Isolate the problem before explaining it. + +## Communication + +- Answer the question asked — no digressions. +- Prefer direct answers over exposition. +- If you need more context, ask rather than guess. +``` + +`system/rules.md`: + +```markdown +## Read-only constraint + +You are in read-only mode. The following are blocked: + +- Creating, modifying, or deleting files +- Shell commands that write to the filesystem +- Git operations that change repository state (commit, push, merge, rebase, + tag, etc.) +- Installing packages, dependencies, or system software +- Modifying any configuration + +## What you can do + +- Read files and search codebases +- Answer questions about code, architecture, design, and conventions +- Analyze bugs, trace execution paths, explain behavior +- Run read-only shell commands: ls, cat, grep, find, git log, git status, + git diff, git show, git blame, and similar +- Debug environments and diagnose issues + +## If asked to write + +Explain that you are in read-only mode and cannot perform write operations. +Suggest switching to a persona with write access via `/persona `. +``` + +### Why override behavior.md + +San's default `behavior.md` carries ~30 lines of engineering methodology: +work habits (read design docs, follow layered architecture, write unit tests, +run lint), communication style (report what you changed, link PRs), and scope +discipline. None of this applies to a read-only session. Overriding it with a +minimal alternative recovers those tokens and gives the model guidance +actually relevant to reading and analyzing. + +### Why override rules.md + +San's default `rules.md` bundles policy, tool protocols, task/git conventions, +safety rules for destructive commands, and provider-specific quirks. For a +read-only session these are dead weight — the persona cannot write anyway, so +rules about commit messages, PR conventions, and `--no-verify` are noise. +A minimal replacement keeps only the readonly constraint. + +### Token savings estimate + +Rough line counts for San's built-in defaults vs. the readonly replacements: + +| Part | Default (approx. lines) | Readonly (approx. lines) | Savings | +|---|---|---|---| +| `identity` | ~3 | ~2 | small | +| `behavior` | ~30 | ~12 | ~60% | +| `rules` | ~120 | ~18 | ~85% | +| **Total** | **~153** | **~32** | **~80%** | + +These savings apply to every turn where the system prompt is included +(including cache misses), and because the persona prompt is part of the +prompt-cache prefix, the smaller prompt also means a smaller cache entry. + +### Read-only operations (allowed) + +| Category | Examples | +|---|---| +| Read files | `Read`, `cat`, `head`, `tail` | +| Search | `grep`, `find`, `git grep` | +| Git read | `git log`, `git status`, `git diff`, `git show`, `git blame` | +| Analyze | code review, architecture analysis, bug tracing | +| Answer | questions about code, design, conventions | +| Debug | trace errors, inspect logs, check environment state | + +### Write operations (blocked) + +| Category | Examples | +|---|---| +| File write | `Edit`, `Write`, shell redirect (`>`, `>>`), `tee` | +| File delete | `rm`, `rmdir`, `shred` | +| File move/copy | `mv`, `cp`, `dd` | +| File create | `touch`, `mkdir` | +| Permissions | `chmod`, `chown` | +| Git write | `commit`, `push`, `merge`, `rebase`, `tag`, `am`, `cherry-pick`, `stash` | +| Package install | `go install`, `npm install`, `pip install`, `make install`, `brew install` | +| Destructive | `rm -rf`, force push, `git reset --hard` | + +## Decision + +### D1: Ship as built-in, not on-disk + +The readonly persona is compiled into the San binary via `embed.FS`, not +scanned from `~/.san/personas/` or `.san/personas/`. It is always available +even on a fresh install with no persona directories. + +It is still registered in the persona registry so `/persona readonly` works +and it appears in the `/persona` selector. + +### D2: Not the system default (initially) + +The readonly persona is **available by default**, but the system default +behavior (when no persona is selected) remains unchanged for now — full access. + +Users opt into readonly mode via `/persona readonly` or by configuring +`persona: readonly` in their settings. Making it the system default is +deferred to a future decision after migration impact is assessed. + +**Rationale**: Changing the default persona breaks the expectation that +a fresh San session has full tool access. This is a migration that requires +communication, not just code. Ship the persona first; flip the default +later if the community agrees. + +### D3: Override all three prose parts — no default fallback + +The persona provides its own `identity.md`, `behavior.md`, and `rules.md`. +None of San's default prose parts are used. This is deliberate: + +- **Token efficiency**: The default parts carry substantial content for + engineering workflows. Dropping them saves ~80% of the system prompt prose + (see estimate above). +- **Signal clarity**: The model receives only instructions relevant to its + read-only task. No engineering methodology, git protocols, or commit + conventions dilute the prompt. +- **No dead rules**: Rules about `--no-verify`, commit message format, branch + naming, and PR descriptions are noise when the persona cannot write. + +Each part is kept minimal — the design goal is the shortest prompt that +produces correct read-only behavior. + +### D4: Enforcement — permissions.deny (primary) + rules.md (advisory) + +Two-layer defense, consistent with the persona model's design philosophy: + +1. **`settings.json` permissions.deny** — the *enforcement* layer. Blocks + write tools at the permission engine before they reach the model. Cannot + be loosened by lower layers (user/project settings), per the persona + permission merge semantics. + +2. **`system/rules.md`** — the *advisory* layer. Natural-language constraints + the model reads each turn. Guides the model away from write operations + even if a tool slips through the deny list. + +```json +{ + "description": "Read-only persona — answers questions, analyzes code, debugs environments. Cannot write.", + "skills": {}, + "agents": [], + "disabledTools": {}, + "permissions": { + "defaultMode": "default", + "deny": [ + "Edit", + "Write", + "Bash(rm:*)", + "Bash(rmdir:*)", + "Bash(mv:*)", + "Bash(cp:*)", + "Bash(touch:*)", + "Bash(mkdir:*)", + "Bash(dd:*)", + "Bash(shred:*)", + "Bash(chmod:*)", + "Bash(chown:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(git merge:*)", + "Bash(git tag:*)", + "Bash(git rebase:*)", + "Bash(git reset:*)", + "Bash(git am:*)", + "Bash(git cherry-pick:*)", + "Bash(git stash:*)", + "Bash(go install:*)", + "Bash(npm install:*)", + "Bash(yarn add:*)", + "Bash(pip install:*)", + "Bash(pip3 install:*)", + "Bash(brew install:*)", + "Bash(make install:*)", + "Bash(curl * | *)", + "Bash(wget -O:*)" + ] + } +} +``` + +### D5: Git hooks — evaluated and deferred + +Git hooks were considered as a third enforcement layer but are deferred: + +- **Pro**: A pre-commit hook could block commits at the Git level, independent + of San's tool permissions. This is defense-in-depth. +- **Con**: Hooks only cover Git operations, not general file writes. The + permission engine already covers the full tool surface. Hooks require + explicit installation per repository. The active persona is a San runtime + concept — hooks have no native way to query it. + +**If needed later**: Ship a `san persona current` CLI command that prints the +active persona. A pre-commit hook can call it: + +```bash +#!/bin/bash +if [ "$(san persona current 2>/dev/null)" = "readonly" ]; then + echo "Commits are blocked in readonly persona mode." + exit 1 +fi +``` + +This is a future extension; not part of this ADR. + +### D6: Subagent inheritance + +When the readonly persona spawns subagents (via the Agent tool), the readonly +constraint propagates through the permission layer — subagents use the same +effective settings overlay. No special subagent handling is needed. + +## Implementation + +### Phase 1 — Embedded persona files + +Create `internal/persona/builtin/readonly/`: + +``` +internal/persona/builtin/readonly/ +├── system/ +│ ├── identity.md +│ ├── behavior.md +│ └── rules.md +└── settings.json +``` + +Embed via `embed.FS`. Register in the persona registry as a built-in entry +that cannot be overridden by on-disk personas of the same name. + +### Phase 2 — `/persona` integration + +- Show "readonly" in the `/persona` selector. +- Support `/persona readonly` to switch. +- Persist the selection so `persona: readonly` in settings survives restart. + +### Phase 3 — Wire prompts into the persona loader + +Write the three prompt files (`identity.md`, `behavior.md`, `rules.md`) as +defined in [Prompt content](#prompt-content) above and embed them alongside +`settings.json`. + +## Consequences + +### Positive + +- **Environment protection**: The working directory is safe from accidental + modification. No file corruption, no unintended git mutations, no + hallucinated destructive commands. +- **Token savings (~80%)**: The persona replaces ~153 lines of default + system prompt prose with ~32 lines of read-only-specific content. The + smaller prompt saves tokens on every turn and reduces prompt-cache size. +- **Signal clarity**: The model only receives instructions relevant to + reading, analyzing, and explaining — no engineering methodology or git + protocols dilute the prompt. +- **Safe starting point**: Users can explore, ask questions, and analyze code + without risk of accidental writes. +- **Intentional escalation**: Write operations require a conscious persona + switch — making the user's intent explicit. +- **No new infrastructure**: Reuses the existing persona system, permission + engine, and settings overlay — no new enforcement mechanisms. + +### Negative / costs + +- **Deny list coverage**: The deny list must explicitly enumerate every write + tool and command pattern. A new write-capable tool added to San (or a + creative shell command not matching the deny patterns) could slip through. + The advisory `rules.md` mitigates but does not eliminate this gap. +- **Not a sandbox**: San personas operate at the user's trust level — this is + a guardrail, not a security boundary. A determined user or a plugin can + bypass it. +- **User confusion**: Users who expect to edit files out of the box will be + blocked when they switch to readonly mode. The persona selector must + clearly communicate what each persona allows. + +## References + +- [`persona.md`](../../concepts/persona.md) — persona system design +- [`permission-model.md`](../../concepts/permission-model.md) — permission engine +- [`ADR-0001`](0001-layered-package-architecture.md) — layered package architecture +- [`ADR-0002`](0002-autonomous-dev-management.md) — autonomous dev team (persona config examples) From 1748a14d8ceb8c8afe90c5017e0f579144f7ee9b Mon Sep 17 00:00:00 2001 From: dangpeng Date: Fri, 19 Jun 2026 10:27:41 +0800 Subject: [PATCH 3/4] docs(design): scope branch to read-only persona ADR only Remove 0002/0003 ADR files that belong to a separate PR. Co-Authored-By: Claude Opus 4.7 Signed-off-by: dangpeng --- .../0002-autonomous-dev-management.md | 708 ++++++----------- .../decisions/0003-shared-work-queue.md | 312 -------- .../0002-autonomous-dev-management.md | 725 ++++++------------ .../decisions/0003-shared-work-queue.md | 325 -------- 4 files changed, 451 insertions(+), 1619 deletions(-) delete mode 100644 docs/cn/design/decisions/0003-shared-work-queue.md delete mode 100644 docs/design/decisions/0003-shared-work-queue.md diff --git a/docs/cn/design/decisions/0002-autonomous-dev-management.md b/docs/cn/design/decisions/0002-autonomous-dev-management.md index 5b2a898e..84445091 100644 --- a/docs/cn/design/decisions/0002-autonomous-dev-management.md +++ b/docs/cn/design/decisions/0002-autonomous-dev-management.md @@ -2,7 +2,7 @@ ## 状态 -提案中 — 2026-06-11. 更新于 2026-06-16. +提案中 — 2026-06-11. ## 背景 @@ -26,13 +26,14 @@ ``` genai-io(Org,已存在) ├── san ← San 源码仓库(已存在,被管理的目标项目) -└── san-team ← 新建的仓库(纯内容,无 Go 代码) +└── san-team ← 新建的仓库(San 开发团队 Persona) ├── leader/ │ ├── system/ │ │ ├── identity.md │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ + │ │ └── ... │ └── settings.json ├── dev/ │ ├── system/ @@ -40,6 +41,7 @@ genai-io(Org,已存在) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ + │ │ └── ... │ └── settings.json ├── qe/ │ ├── system/ @@ -47,21 +49,21 @@ genai-io(Org,已存在) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ + │ │ └── ... │ └── settings.json - ├── release/ - │ ├── system/ - │ │ ├── identity.md - │ │ ├── behavior.md - │ │ └── rules.md - │ ├── skills/ - │ └── settings.json - ├── state/ ← queue.jsonl 存放位置 - └── run.sh ← 轮询循环(12 行 shell 脚本) -``` - -- **`san-team`**:纯内容仓库 —— 无 Go 代码、无二进制。只包含 - persona markdown 文件、共享状态目录和轮询 shell 脚本。 - 所有 Go 逻辑都在 San 中,作为可复用的独立功能。 + └── release/ + ├── system/ + │ ├── identity.md + │ ├── behavior.md + │ └── rules.md + ├── skills/ + │ └── ... + └── settings.json +``` + +- **`san-team`**:新建仓库,包含 San 项目的开发团队。 + Persona 直接放在仓库根目录下。如果其他项目需要类似的自主管理, + 可以创建独立的团队仓库(如 `devops-team`)。 - **团队(Team)**:`san-team` 仓库中的一组 Persona, 为特定目标协同工作 —— 管理 San 项目的 issue、feature、bug 和发布。 - **Persona 目录**:每个 Persona 遵循 persona 规范的三层结构: @@ -71,215 +73,127 @@ genai-io(Org,已存在) ## 运行模型 -每个 Persona 以**独立的 San 实例**运行。根据角色不同,启动方式有区别: - -### Leader —— 交互式 TUI - -Leader 是管理员的对话入口,以标准交互式 San 会话运行: +每个 Persona 以**独立的 San 实例**运行: ```bash -san --persona leader -``` +# 启动 Leader Persona(管理员交互入口) +san start --persona leader --team san-team -使用 San 现有的 `--persona` 参数,从 `~/.san/personas/leader/` -(指向 `san-team/leader/` 的软链接)加载 persona 配置。 +# 启动 Dev Persona(等待编码任务) +san start --persona dev --team san-team -### Dev, QE, Release —— 通过 run.sh 无头运行 - -这些 persona 通过 `run.sh` 启动,以 `san --persona -p` 方式无头运行。 -不需要交互式终端 —— 它们轮询队列、领取任务、执行任务、更新队列: - -```bash -# 终端 2:启动 Dev 轮询循环 -./run.sh dev /path/to/san +# 启动 QE Persona(等待验证任务) +san start --persona qe --team san-team -# 终端 3:启动 QE 轮询循环 -./run.sh qe /path/to/san - -# 终端 4:启动 Release 轮询循环 -./run.sh release /path/to/san -``` - -`run.sh` 是一个简单的 shell 脚本(见下文)。每个 persona 的系统提示词 -(identity + behavior + rules)由 `san --persona -p` 从 -`~/.san/personas//` 加载。 - -### 安装:链接 Persona - -启动前,将 san-team 中的 persona 链接到 San 的 persona 目录: - -```bash -ln -s /path/to/san-team/leader ~/.san/personas/leader -ln -s /path/to/san-team/dev ~/.san/personas/dev -ln -s /path/to/san-team/qe ~/.san/personas/qe -ln -s /path/to/san-team/release ~/.san/personas/release +# 启动 Release Persona(等待发布任务) +san start --persona release --team san-team ``` -所有 persona —— Leader、Dev、QE、Release —— 统一使用 `~/.san/personas/` 机制。 -不需要额外的 persona 加载代码。 +`--persona` 参数告诉 San 在启动时加载哪个 Persona 目录的配置 +(system/ + skills/ + settings.json),不需要在运行中通过 `/persona` 命令切换。 -## run.sh —— 粘合层 - -`run.sh` 是 san-team 中唯一的"代码"。它是 `san queue` 和 `san -p` 之间的桥梁: - -```bash -#!/bin/bash -set -euo pipefail -TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" -PERSONA="${1:?usage: $0 }" -CWD="${2:-$(pwd)}" -INTERVAL="${3:-30}" - -echo "[san-team:$PERSONA] 每 ${INTERVAL}s 轮询一次, cwd=$CWD" - -while true; do - # 1. 原子认领下一个 pending 任务 - TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) - if [ -n "$TASK" ]; then - ID=$(echo "$TASK" | jq -r '.id') - TITLE=$(echo "$TASK" | jq -r '.title') - DESC=$(echo "$TASK" | jq -r '.description') - echo "[san-team:$PERSONA] 认领 $ID: $TITLE" - - # 2. 构建任务提示词,无头运行 Agent - PROMPT="Task: $TITLE - - $DESC - - 完成后,总结你的工作内容和 PR 链接。" - - if san --persona "$PERSONA" -p "$PROMPT"; then - san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] 完成 $ID" - else - san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] 释放 $ID(将重试)" - fi - fi - sleep "$INTERVAL" -done -``` - -关键设计要点: -- **队列操作是 shell 的职责**:`run.sh` 调用 `san queue claim`、 - `san queue complete`、`san queue release`。LLM Agent 完全不知道队列的存在。 -- **Persona 加载是 San 的职责**:`san --persona -p` 从 - `~/.san/personas/` 加载 persona 系统提示词。Shell 不需要拼装 persona 文件。 -- **LLM 推理是 San 的职责**:`san -p` 用任务描述作为用户消息来执行 Agent。 -- **Shell 只是胶水**:认领 → 运行 → 完成/释放。12 行代码。 +多个 Persona 实例可以同时运行在不同的终端、容器或机器上。 +它们通过共享工作队列(`san-team/state/queue.jsonl`)协调工作。 ## 工作流程 -管理员只跟 Leader Persona 对话。Leader 把需求拆成 Task, -通过 `san queue add` 写入共享队列。其他 Persona 通过 -`run.sh` 轮询队列,认领匹配的任务,完成后更新队列状态。 +管理员只跟 Leader Persona 对话。Leader 把需求拆成 Task 写入共享队列, +其他 Persona 从队列领取任务,完成后更新队列状态。 ``` 管理员(人) │ - │ "实现用户登录功能" + │ "实现用户登录功能" or "修复所有 P0 Bug" ▼ -┌────────────────────────────────────────────┐ -│ Leader(san --persona leader) │ -│ │ -│ 1. 理解需求 │ -│ 2. 画架构图、状态图(向管理员确认) │ -│ 3. 拆解为 Task │ -│ 4. 写入队列:san queue add ... │ -│ 5. 监控:san queue list ... │ -└────────────────┬───────────────────────────┘ - │ 共享工作队列 (state/queue.jsonl) - ┌────────┼────────┐ - ▼ ▼ ▼ - ┌──────────┐ ┌──────┐ ┌─────────┐ - │ run.sh │ │run.sh│ │ run.sh │ - │ dev │ │ qe │ │ release │ - │ │ │ │ │ │ - │ claim→ │ │claim→│ │claim→ │ - │ san -p │ │agent │ │ san -p │ - │ --persona│ │run │ │--persona│ - │ dev │ │--qe │ │ release │ - │ →complete│ │→verify│ │→complete│ - └──────────┘ └──────┘ └─────────┘ +┌──────────────────────────────────────────────────────┐ +│ Leader Persona(san start --persona leader --team san-team)│ +│ │ +│ 1. 理解需求 │ +│ 2. 画架构图、状态图(向管理员确认) │ +│ 3. 拆解为 Task,写入共享队列 │ +│ 4. 监控队列,收集完成结果,向管理员汇报 │ +└──────────────────┬───────────────────────────────────┘ + │ 共享工作队列 (state/queue.jsonl) + ┌────────┼────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────┐ ┌─────────┐ + │Dev│ │ QE │ │ Release │ + │ san start│ │san start│ │san start│ + │ --persona│ │--persona│ │--persona│ + │dev│ │ qe │ │ release │ + └──────────┘ └──────┘ └─────────┘ ``` ### Leader Persona —— 唯一入口 -Leader 通过 `san --persona leader` 启动, +Leader 通过 `san start --persona leader --team san-team` 启动, 是管理员唯一的交互界面。管理员告诉 Leader 要做什么, Leader 负责: 1. **理解需求**:新功能?Bug 修复?重构? 2. **分析 San 项目**:读取 San 源码仓库中的设计文档和现有代码 3. **可视化**:画 mermaid 架构图、状态图,向管理员确认理解 -4. **拆解**:把功能拆成多个 Task,通过 `san queue add --dir state/ --role dev --title "..." --description "..."` 写入队列 -5. **监控**:通过 `san queue list --dir state/` 跟踪队列中的 Task 状态 +4. **拆解**:把功能拆成多个 Task,写入共享工作队列 +5. **监控**:跟踪队列中 Task 的状态变化 6. **汇报**:收集完成结果,向管理员汇总 Leader 不自己写代码。Leader 把 Task 写入队列后, -由对应 Persona 的 `run.sh` 循环自动领取执行。 +由对应 Persona 的 San 实例自动领取执行。 ``` Leader 派发编码任务示例: Leader: 1. 分析需求后,确定 Task-3 是编码任务 - 2. 运行:san queue add --dir state/ --role dev --title "实现 JWT..." - 3. Dev 的 run.sh 轮询队列,发现 Task-3 匹配自己的角色 - 4. Dev 通过 san queue claim 认领,运行 san --persona dev -p ".." - 5. Agent 完成,run.sh 调用 san queue complete 附上 PR 链接 - 6. Leader 通过 san queue list 看到 Task-3 已完成 + 2. 将 Task-3 写入队列(标记 role: dev) + 3. Dev San 实例轮询队列,发现 Task-3 匹配自己的角色 + 4. Dev 认领 Task-3,开始实现 + 5. 完成后更新队列状态为 done,附上 PR 链接 + 6. Leader 轮询发现 Task-3 已完成,继续下一步 ``` ### Dev Persona —— 编码实现 -由 `run.sh dev` 启动,持续轮询队列中的编码任务: - -1. `run.sh` 通过 `san queue claim` 认领 role 为 dev 的 Task -2. 从任务描述构建提示词 -3. 运行 `san --persona dev -p "..." -4. Agent 读取 San 项目的设计文档和现有代码 -5. Agent 遵循分层架构规范写代码 -6. Agent 写单元测试(同包内) -7. Agent 跑 `make test` + `make lint` 通过 -8. Agent 提交代码、创建 PR -9. 成功:`run.sh` 调用 `san queue complete --id $ID --pr ` -10. 失败:`run.sh` 调用 `san queue release --id $ID` +通过 `san start --persona dev --team san-team` 启动, +持续轮询队列中的编码任务: -Dev Agent 完全不接触队列 —— `run.sh` 根据 Agent 的退出码处理所有队列操作。 +1. 从队列认领 role 为 dev 的 Task +2. 读取 San 项目的设计文档和现有代码 +3. 遵循分层架构规范写代码 +4. 写测试 +5. 跑 `make test` + `make lint` 通过 +6. 提交代码、创建 PR +7. 更新队列状态为 done,附上 PR 链接 ### QE Persona —— 验证测试 -由 `run.sh qe` 启动,持续轮询队列中的验证任务: +通过 `san start --persona qe --team san-team` 启动, +持续轮询队列中的验证任务: -1. `run.sh` 认领 role 为 qe 的 Task -2. 运行 `san --persona qe -p "..." -3. Agent 检出 PR 分支 -4. Agent 跑全量测试 + lint + layer check -5. Agent 在 `test-integration/` 中添加集成测试,提交到同一 PR -6. Agent 发 PR Review -7. 通过:`run.sh` 调用 `san queue verify --id $ID --result "passed"` -8. 失败:`run.sh` 调用 `san queue release --id $ID --reason "..."` +1. 从队列认领 role 为 qe 的 Task(对应 Dev 已完成) +2. 检出 PR 分支 +3. 跑全量测试 + lint + layer check +4. 用 `verify` 技能确认功能正确 +5. 发 PR Review +6. 更新队列状态:通过(verified)或失败(附原因) -QE 也可以在开发前介入:Leader 写好架构图后,先创建验证设计的 Task 给 QE。 +也可以在开发前介入:Leader 写好架构图后,先创建验证设计的 Task 给 QE。 ### Release Persona —— 发布上线 -由 `run.sh release` 启动,持续轮询队列中的发布任务: +通过 `san start --persona release --team san-team` 启动: -1. `run.sh` 认领 role 为 release 的 Task -2. 运行 `san --persona release -p "..." -3. Agent 生成 CHANGELOG -4. Agent 更新版本号 -5. Agent 打 Git Tag -6. Agent 生成 Release Notes -7. 成功:`run.sh` 调用 `san queue complete --id $ID` +1. 从队列认领 role 为 release 的 Task(全部 QE 通过后) +2. 生成 CHANGELOG +3. 更新版本号 +4. 打 Git Tag +5. 生成 Release Notes +6. 更新队列状态为 done ### 共享工作队列 队列是 Persona 之间的唯一通信机制,文件位于 `san-team/state/queue.jsonl`(JSONL 追加日志)。 -所有操作通过 `san queue` 子命令完成(详见 [ADR-0003](0003-shared-work-queue.md))。 ``` type WorkItem struct { @@ -293,264 +207,15 @@ type WorkItem struct { Result string // 结果说明(QE/Release 填充) CreatedAt time.Time UpdatedAt time.Time - ClaimedAt time.Time - RetryCount int - MaxRetries int } ``` 状态流转: ``` -pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified - ↑ │ │ - └──release()─── ← (超时) ← reject() ──┘ - │ - └──fail()──→ failed -``` - -## 完整安装运行示例 - -从零到团队运行的完整步骤。 - -### 前提条件 - -```bash -# San CLI 已编译且在 PATH 中 -cd /path/to/san -make build -export PATH="$PATH:$PWD/bin" - -# san-team 仓库克隆到 san 同级目录 -cd /path/to -git clone git@github.com:genai-io/san-team.git -``` - -### 第一步:链接 Persona - -```bash -ln -s /path/to/san-team/leader ~/.san/personas/leader -ln -s /path/to/san-team/dev ~/.san/personas/dev -ln -s /path/to/san-team/qe ~/.san/personas/qe -ln -s /path/to/san-team/release ~/.san/personas/release -``` - -### 第二步:启动 Leader(交互式) - -```bash -# 终端 1:Leader 会话 -cd /path/to/san -san --persona leader -``` - -管理员在这里与 Leader 交互。例如: - -``` -管理员:实现用户认证功能,支持 JWT 登录 - -Leader: - 读取 docs/design/ → 分析代码库 → 画时序图 - 与管理员确认 → 拆解为 Task: - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "定义 User 模型和 UserStore 接口" \ - --description "在 internal/core/user.go 中创建 User struct..." - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "基于 SQLite 实现 UserStore" \ - --description "在 internal/feature/userstore/ 中实现 UserStore 接口..." - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "实现 JWT token 生成与验证" \ - --description "创建 internal/core/jwt/ 包..." - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "实现 POST /auth/login handler" \ - --description "创建登录 API handler..." - - $ san queue add --dir /path/to/san-team/state --role qe \ - --title "验证认证模块(Task 1-4)" \ - --description "检出各 PR,运行测试,添加集成测试..." - - $ san queue add --dir /path/to/san-team/state --role release \ - --title "发布 v1.3.0" \ - --description "生成 CHANGELOG,更新版本号,打 tag v1.3.0..." -``` - -### 第三步:启动工作 Persona(无头模式) - -```bash -# 终端 2:Dev 轮询循环 -cd /path/to/san-team -./run.sh dev /path/to/san 30 - -# 终端 3:QE 轮询循环 -cd /path/to/san-team -./run.sh qe /path/to/san 30 - -# 终端 4:Release 轮询循环 -cd /path/to/san-team -./run.sh release /path/to/san 60 -``` - -### 第四步:查看进度 - -```bash -# 任意终端:查看队列状态 -san queue list --dir /path/to/san-team/state -``` - -输出示例: - -``` -ID Role 标题 状态 负责人 PR -a1b2c3d4 dev 定义 User 模型和 UserStore 接口 done dev #123 -b2c3d4e5 dev 实现 UserStore(SQLite) done dev #124 -c3d4e5f6 dev 实现 JWT token 生成 claimed dev - -d4e5f6a7 dev 实现 POST /auth/login pending - - -a7b8c9d0 qe 验证认证模块(Task 1-4) pending - - -b8c9d0e1 release 发布 v1.3.0 pending - - - -Total: 6 | pending: 3 | claimed: 1 | done: 2 | verified: 0 | failed: 0 -``` - -### run.sh 内部运行过程 - -当 Dev 的 `run.sh` 轮询发现一个 pending 任务时: - -``` -1. san queue claim --dir state/ --role dev --persona dev - → 返回 JSON: {"id":"d4e5f6a7...", "title":"实现 POST /auth/login", ...} - → 任务状态变为 "claimed" - -2. san --persona dev -p ".." --prompt "Task: 实现 POST /auth/login - 创建登录 API handler..." --cwd /path/to/san - → Agent 从 ~/.san/personas/dev/ 加载 dev persona - → Agent 实现 handler + 单元测试 - → Agent 创建 PR #126 - → Agent 退出码 0(成功) - -3. san queue complete --dir state/ --id d4e5f6a7 --persona dev \ - --pr "https://github.com/genai-io/san/pull/126" - → 任务状态变为 "done" - -4. sleep 30,重复 -``` - -如果 Agent 执行失败: - -``` -1. san queue claim ... (同上) -2. san --persona dev -p "..." - → Agent 遇到错误,退出码 1(失败) -3. san queue release --dir state/ --id d4e5f6a7 --persona dev - → 任务状态回退 "pending",retryCount++ - → 下次轮询会再次认领(最多重试 maxRetries 次) -``` - -## 完整工作流示例 - -管理员输入:**"实现用户认证功能,支持 JWT 登录"** - -### 1. Leader 理解需求 + 画架构图 - -Leader 读取 San 项目的 `docs/design/`,分析已有代码, -生成 mermaid 时序图: - -```mermaid -sequenceDiagram - participant Client - participant AuthHandler - participant AuthService - participant UserStore - participant JWT - - Client->>AuthHandler: POST /auth/login - AuthHandler->>AuthService: Login(username, password) - AuthService->>UserStore: FindByUsername(username) - UserStore-->>AuthService: User - AuthService->>AuthService: Verify password - AuthService->>JWT: GenerateToken(user) - JWT-->>AuthService: token - AuthService-->>AuthHandler: token - AuthHandler-->>Client: 200 { token } -``` - -Leader 展示图给管理员:"这是我理解的功能流程,对吗?" - -### 2. 拆解 Task 并写入队列 - -管理员确认后,Leader 将 Task 写入共享队列: - -``` -$ san queue add --dir state/ --role dev --title "定义 User 模型和 UserStore 接口" \ - --description "在 internal/core/user.go 中创建 User struct(ID, Username, PasswordHash, CreatedAt)..." - -$ san queue add --dir state/ --role dev --title "基于 SQLite 实现 UserStore" \ - --description "在 internal/feature/userstore/ 中实现 UserStore 接口..." - -$ san queue add --dir state/ --role dev --title "实现 JWT token 生成与验证" \ - --description "创建 internal/core/jwt/ 包..." - -$ san queue add --dir state/ --role dev --title "实现登录 API Handler" \ - --description "在 internal/app/authhandler/ 中创建登录 API handler..." - -$ san queue add --dir state/ --role qe --title "验证全部认证功能" \ - --description "检出 PR,运行测试,添加集成测试..." - -$ san queue add --dir state/ --role release --title "发布 v1.3.0" \ - --description "生成 CHANGELOG,更新版本号,打 tag v1.3.0..." -``` - -### 3. 各 Persona 自动领取执行 - -``` -Dev 的 run.sh 轮询队列: - 认领 Task 1 → san --persona dev -p ".." → 实现 → PR #123 → complete - 认领 Task 2 → san --persona dev -p ".." → 实现 → PR #124 → complete - 认领 Task 3 → san --persona dev -p ".." → 实现 → PR #125 → complete - 认领 Task 4 → san --persona dev -p ".." → 实现 → PR #126 → complete - -QE 的 run.sh 轮询队列: - 发现 Task 1-4 均为 done → 认领 Task 5 - san --persona qe -p ".." → 检出 PR → 运行测试 → 添加集成测试 - → 通过 → san queue verify - -Leader 监控到全部 verified → 通知管理员 approve PR -管理员审批通过 → Leader 运行: - $ san queue add --dir state/ --role release --title "发布 v1.3.0" ... - -Release 的 run.sh 轮询队列: - 认领 Task 6 → san --persona release -p ".." → changelog → tag → complete - -Leader → 管理员: "认证功能已全部完成并发布,PR: #123-#126" -``` - -## Bug 修复流程 - -管理员对 Leader 说:**"扫描并修复所有 P0 Bug"** - -``` -Leader: - 1. 通过 gh 拉取 San 项目所有 P0 Bug issues - 2. 逐个分析,写入队列: - $ san queue add --dir state/ --role dev --title "修复 #100 nil pointer in auth.go" ... - $ san queue add --dir state/ --role dev --title "修复 #102 timeout in db query" ... - -Dev 的 run.sh: - 认领 "#100" → san --persona dev -p ".." → 修复 → PR → complete - 认领 "#102" → san --persona dev -p ".." → 修复 → PR → complete - -QE 的 run.sh: - 认领 "#100 验证" → san --persona qe -p ".." → 验证 → 通过 - 认领 "#102 验证" → san --persona qe -p ".." → 验证 → 通过 - -Leader 通知管理员 approve → 管理员审批通过 - -Release 的 run.sh: - 认领 "hotfix 发布" → changelog → tag → complete - -Leader → 管理员: "2 个 P0 Bug 已全部修复并发布" +pending ──→ claimed ──→ done ──→ verified + │ │ + └──→ (timeout) ──→ pending(超时未完成,释放回队列) ``` ## Persona 配置示例 @@ -561,7 +226,7 @@ Leader → 管理员: "2 个 P0 Bug 已全部修复并发布" ```markdown 你是 San 项目的开发 Agent。 -你的职责是实现分配给你的编码任务。 +你的职责是从共享队列领取编码任务并完成实现。 你擅长 Go 开发,熟悉 San 的五层包架构。 ``` @@ -570,16 +235,16 @@ Leader → 管理员: "2 个 P0 Bug 已全部修复并发布" ```markdown ## 工作方式 -1. 先读取相关的设计文档和现有代码,理解上下文后再动手 -2. 遵循 internal/ 五层架构的依赖方向:cmd → app → feature → core → infrastructure -3. 每个变更必须在同包内包含单元测试 -4. 完成后运行 make test 和 make lint,确保通过后再提交 -5. 用清晰的信息提交代码、创建 PR,报告 PR 链接 +1. 持续轮询共享队列,认领匹配自己角色的 Task +2. 先读取相关的设计文档和现有代码,理解上下文后再动手 +3. 遵循 internal/ 五层架构的依赖方向:cmd → app → feature → core → infrastructure +4. 每个变更必须包含测试 +5. 完成后运行 make test 和 make lint,确保通过后再提交 ## 沟通风格 -- 遇到不确定的设计决策,在结果中记录问题供 Leader 参考 -- 完成后在结果中附上简洁明确的说明:做了什么、改了哪些文件、PR 链接 +- 遇到不确定的设计决策,更新 Task 状态,附上问题等 Leader 回复 +- 完成后在 Task 中附上简洁明确的说明:做了什么、改了哪些文件、PR 链接 - 不要做超出 Task 范围的事 ``` @@ -668,6 +333,99 @@ Leader → 管理员: "2 个 P0 Bug 已全部修复并发布" } ``` +## 完整工作流示例 + +管理员输入:**"实现用户认证功能,支持 JWT 登录"** + +### 1. Leader 理解需求 + 画架构图 + +Leader 读取 San 项目的 `docs/design/`,分析已有代码, +生成 mermaid 时序图: + +```mermaid +sequenceDiagram + participant Client + participant AuthHandler + participant AuthService + participant UserStore + participant JWT + + Client->>AuthHandler: POST /auth/login + AuthHandler->>AuthService: Login(username, password) + AuthService->>UserStore: FindByUsername(username) + UserStore-->>AuthService: User + AuthService->>AuthService: Verify password + AuthService->>JWT: GenerateToken(user) + JWT-->>AuthService: token + AuthService-->>AuthHandler: token + AuthHandler-->>Client: 200 { token } +``` + +Leader 展示图给管理员:"这是我理解的功能流程,对吗?" + +### 2. 拆解 Task 并写入队列 + +管理员确认后,Leader 将 Task 写入共享队列: + +``` +队列写入: + Task 1: { role: dev, title: "定义 User 模型和 UserStore 接口" } + Task 2: { role: dev, title: "实现 UserStore" } + Task 3: { role: dev, title: "实现 JWT token 生成与验证" } + Task 4: { role: dev, title: "实现登录 API Handler" } + Task 5: { role: qe, title: "验证全部认证功能" } + Task 6: { role: release, title: "发布 v1.2.0" } +``` + +### 3. 各 Persona 自动领取执行 + +``` +Dev San 实例轮询队列: + 认领 Task 1 → 实现 → 标记 done + 认领 Task 2 → 实现 → 标记 done + 认领 Task 3 → 实现 → 标记 done + 认领 Task 4 → 实现 → 标记 done + +QE San 实例轮询队列: + 发现 Task 1-4 均为 done → 认领 Task 5 + 检出 PR 分支 → 跑测试 → 通过 → 标记 verified + +Leader 监控到全部 verified → 通知管理员 approve PR +管理员审批通过后 → Leader 写入 Task 6 + +Release San 实例轮询队列: + 认领 Task 6 → 生成 CHANGELOG → 打 tag → 标记 done + +Leader → 管理员: "认证功能已全部完成并发布,PR: #1234" +``` + +## Bug 修复流程 + +管理员对 Leader 说:**"扫描并修复所有 P0 Bug"** + +``` +Leader: + 1. 通过 GhCLI 拉取 San 项目所有 P0 Bug issues + 2. 逐个分析,写入队列: + - { role: dev, title: "修复 #100 nil pointer in auth.go" } + - { role: dev, title: "修复 #102 timeout in db query" } + +Dev San 实例: + 认领 "#100" → 分析根因 → 修复 → PR → 标记 done + 认领 "#102" → 分析根因 → 修复 → PR → 标记 done + +QE San 实例: + 认领 "#100 验证" → 测试 → Review PR → 通过 → 标记 verified + 认领 "#102 验证" → 测试 → Review PR → 通过 → 标记 verified + +Leader 通知管理员 approve → 管理员审批通过 + +Release San 实例: + 认领 "hotfix 发布" → 生成 CHANGELOG → 打 tag → 标记 done + +Leader → 管理员: "2 个 P0 Bug 已全部修复并发布" +``` + ## 关键设计决策 ### 1. Persona 目录 @@ -683,42 +441,31 @@ Persona 定义放在独立仓库 `san-team`,与 San 源码仓库分离: - 可以对 san-team 仓库设置不同的访问权限 - 团队 Persona 可以管理多个目标仓库(未来扩展) -### 3. san-team 是纯内容仓库 —— 无 Go 代码 - -san-team 只包含 markdown 文件、`state/` 和 shell 脚本。所有 Go 逻辑都在 San 中, -作为可复用的独立功能: -- `san queue` —— 原子工作队列操作(详见 [ADR-0003](0003-shared-work-queue.md)) -- `san --persona -p` —— 带 persona 系统提示词的无头 Agent - -这种分离意味着 san-team 没有构建步骤、没有依赖,任何人都可以修改 markdown 和 shell 脚本。 - -### 4. Leader 是唯一入口 +### 3. Leader 是唯一入口 管理员不直接跟 Dev/QE/Release 交互,所有指令给 Leader: - 管理员心智模型简单:只有一个对话对象 - Leader 有全局视角,决定优先级和冲突处理 - 其他 Persona 只关注队列中的任务,无需理解全局 -### 5. 每个 Persona 是独立的 San 实例 - -每个 Persona 启动一个独立的 San 进程: -- **Leader**:`san --persona leader` — 交互式 TUI,与管理员对话 -- **Dev/QE/Release**:`san --persona -p` — 无头模式,由 `run.sh` 启动 +### 4. 每个 Persona 是独立的 San 实例 -优点: +不是通过 Agent 工具嵌套调用子 Agent,而是每个 Persona 启动一个独立的 +San 进程(`san start --persona --team `): - 进程级隔离:每个 Persona 有独立的上下文、工具集、权限 - 可以部署在不同机器/容器上,独立扩缩 - 通过共享工作队列(文件)协调,不需要进程间 RPC -- 所有 persona 加载统一在 `~/.san/personas/` 下 +- 与 persona 的 `/persona` 切换机制互补: + `/persona` 用于交互式场景中热切换角色,`--persona` 用于启动时就固定角色 -### 6. 架构图是沟通语言 +### 5. 架构图是沟通语言 Leader 在动手之前先画 mermaid 图: - 管理员确认理解(避免做错方向) - Dev Persona 有清晰的实现参考(图随 Task 描述一起写入队列) - QE Persona 有验证依据(按图检查完整性) -### 7. Persona 自我进化 +### 6. Persona 自我进化 每个 Persona 在项目中持续学习和自我改进。进化过程通过更新 `san-team` 仓库中的 Persona 配置来固化。 @@ -755,13 +502,11 @@ Task 完成 → Persona 写复盘记录 → 发现可改进项 | 新概念 | 对应现有 / 规划中的机制 | |---|---| -| Persona 目录 | `~/.san/personas/`(现有 persona 系统) | -| `san --persona leader` | 现有 `--persona` 参数(无变化) | -| `san --persona -p` | 现有:`-p` 打印模式配合 `--persona`(PR #231) | -| 共享工作队列 | 新增:`san queue` 子命令(详见 ADR-0003) | -| run.sh 轮询循环 | san-team 中 12 行 shell 脚本 | -| Persona 通信 | 通过 `san queue` 队列轮询,无需进程间 RPC | -| Persona 权限 | settings.json 中 permissions(现有机制) | +| Persona 目录 | Persona 目录(`persona-system.md` 规范) | +| `san start --persona` | 启动时指定 persona,避免启动后热切换 | +| 共享工作队列 | 新增:文件系统 JSONL 队列(`state/queue.jsonl`) | +| Persona 通信 | 通过队列轮询,无需进程间 RPC | +| Persona 权限 | settings.json 中 permissions(deny 只增不减) | ## 实现计划 @@ -770,38 +515,37 @@ Task 完成 → Persona 写复盘记录 → 发现可改进项 - 在 `genai-io` Org 下创建 `san-team` 仓库 - 按 persona 规范编写四套 Persona(leader/dev/qe/release) - 每套包含 `system/{identity,behavior,rules}.md` + `skills/` + `settings.json` -- 创建 `run.sh` 轮询脚本 -- 创建 `state/` 目录(queue.jsonl 在运行时创建) -### 阶段二:添加 `san queue` 子命令 +### 阶段二:`san start --persona` 功能 -- 实现 `internal/queue/` 包(item.go, queue.go, claim.go)— ~200 行 -- 接入 `cmd/san/queue.go` cobra 子命令 -- 命令:list, claim, add, complete, verify, release, fail -- 详见 [ADR-0003](0003-shared-work-queue.md) +- San CLI 新增 `--persona` 和 `--team` 参数 +- 启动时加载指定团队下的 Persona 配置 +- 加载 system/ 文件作为系统提示词 +- 加载 skills/ 作为活跃技能 +- 应用 settings.json 中的权限和模型配置 -### 阶段三:`--persona` + `-p` 集成(已完成,PR #231) +### 阶段三:共享工作队列 -- 修复 `runPrint`,使 `--persona` 和 `-p` 同时提供时加载 persona 系统提示词 -- 从 `~/.san/personas//` 加载 persona,通过 `system.Build()` 构建包含了 - persona identity/behavior/rules 的系统提示词 -- 应用 persona 的 settings.json 中的 model 和 disabledTools -- 这替代了 `run.sh` 需要拼装 persona 文件到 prompt 的需求 +- 实现 JSONL 格式的持久化队列(`state/queue.jsonl`) +- 队列写入(Leader 创建 Task) +- 队列轮询(Persona 认领匹配角色的 Task) +- 状态流转(pending → claimed → done → verified) +- 超时释放(claimed 超过 N 分钟未完成,自动回退 pending) -### 阶段四:端到端工作流串联 +### 阶段四:工作流串联 -- Leader: `san --persona leader` → 理解需求 → 拆解 → `san queue add` -- Dev: `run.sh dev` → `san queue claim` → `san --persona dev -p ".."` → `san queue complete` -- QE: `run.sh qe` → `san queue claim` → `san --persona qe -p ".."` → `san queue verify` -- Release: `run.sh release` → `san queue claim` → `san --persona release -p ".."` → `san queue complete` -- Leader: `san queue list` → 监控 → 向管理员汇报 +- Leader 设计文档 → 拆解 Task → 写入队列 +- Dev Persona 轮询 → 领取 → 实现 → 提交 → 更新队列 +- QE Persona 轮询 → 领取 → 验证 → 更新队列 +- Release Persona 轮询 → 领取 → 发布 → 更新队列 +- Leader 监控队列状态 → 向管理员汇报 ### 阶段五:自动触发与运维 - Cron 定时 Bug 扫描 → 自动写入队列 - 设计文档合并后自动触发 Leader 拆解 -- 进度可视化(`san queue list`) -- `san queue compact` 用于 JSONL 压缩 +- 进度可视化(CLI:`san team status`) +- Persona 实例健康监控 ## 参考技能 @@ -855,22 +599,18 @@ Task 完成 → Persona 写复盘记录 → 发现可改进项 2. **Leader 合并权限**:所有代码必须由管理员手动 approve 才能合并,不允许自动 merge。 QE 通过后 Leader 通知管理员来审批,管理员点一下 approve 就行。 3. **失败重试策略**:由 Leader 决定每个 Task 的最大重试次数(默认 3 次)。 - Dev 重试耗尽仍未通过 QE 时,`run.sh` 调用 `san queue fail`, - 将 Task 标记为 failed。Leader 检测到后通知管理员介入。 + Dev 重试耗尽仍未通过 QE 时,Leader 将 Task 标记为 failed, + 记录失败原因,通知管理员介入。 4. **崩溃恢复**:所有异常情况(Persona 实例崩溃、Leader 崩溃、队列文件损坏等) 都必须妥善处理,并向管理员汇报当前状态。包括: - Persona 崩溃:claimed 状态 Task 超时自动回退 pending,Leader 检测到后通知管理员 - Leader 崩溃:管理员重新启动 Leader 后,Leader 回放队列恢复上下文 - 队列文件损坏:从 Git 历史恢复最近一次正常快照 - - `run.sh` 崩溃:直接重启脚本即可,从上次中断处继续(已认领的 Task 超时后自动回退) ## 参考资料 - [`persona-system.md`](../../notes/active/persona-system.md) — Persona 目录遵循的 persona 设计规范 -- [ADR-0003](0003-shared-work-queue.md) — 共享工作队列设计(`san queue`) -- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — prompt-first san-team 设计 -- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — 队列示例 - [`core.Agent`](../../packages/core.md) — 底层 Agent 原语 - [`packages/subagent.md`](../../packages/subagent.md) — 子 Agent 机制 - [`packages/skill.md`](../../packages/skill.md) — 技能加载 diff --git a/docs/cn/design/decisions/0003-shared-work-queue.md b/docs/cn/design/decisions/0003-shared-work-queue.md deleted file mode 100644 index 05145f8b..00000000 --- a/docs/cn/design/decisions/0003-shared-work-queue.md +++ /dev/null @@ -1,312 +0,0 @@ -# ADR-0003:多角色协调共享工作队列 - -## 状态 - -提议 — 2026-06-16。 - -## 背景 - -ADR-0002 定义了一个自主开发管理模型,多个角色(Leader、Dev、QE、Release)以独立的 -San 实例运行,通过共享工作队列进行协调。 - -队列需要满足以下条件: -- 允许多个独立进程安全地领取和更新任务,无需中心服务器或数据库 -- 人类可读、可 Git 版本控制,以支持崩溃恢复 -- 支持任务生命周期状态机:pending → claimed → done → verified -- 可与 shell 脚本(`run.sh` 轮询循环)配合使用,无需在 team 仓库中放置 Go 代码 - -## 决策 - -在 San CLI 中新增 `san queue` 子命令,实现**基于文件的 JSONL 工作队列**。 - -### 与 Persona 系统的关系 - -`san queue` 和 `san --persona -p` 是独立、可组合的功能: - -- **`san queue`** 管理共享工作队列:add、claim、complete、verify、release、fail、 - list。它完全不感知 persona 或 LLM Agent。 -- **`san --persona -p`** 从 `~/.san/personas/` 加载 persona, - 注入其系统提示词(identity + behavior + rules),无头运行 Agent。 - 它完全不知道队列的存在。 - -san-team 中的 `run.sh` 脚本将两者组合在一起: - -``` -run.sh → san queue claim → san --persona -p ".."→ san queue complete/release -``` - -这种分离意味着 `san queue` 可以独立于 san-team 使用 —— 任何多 Agent 编排场景、 -CI 流水线或任务调度器都可以复用。 - -### 存储格式 - -``` -/state/queue.jsonl -``` - -每行是一个紧凑的 JSON 对象。状态变更追加新行(相同 ID,更新字段)。读取时按 ID -去重,保留最后一行。这样队列可以轻松从 Git 历史中恢复(`git checkout -- -state/queue.jsonl`)。 - -### 工作项结构 - -```json -{ - "id": "a1b2c3d4e5f6a7b8", - "role": "dev", - "title": "实现 JWT 令牌生成", - "description": "创建 internal/core/jwt/ 包...", - "status": "claimed", - "assignedTo": "dev", - "pr": "", - "result": "", - "createdAt": "2026-06-16T10:00:00Z", - "updatedAt": "2026-06-16T10:30:00Z", - "claimedAt": "2026-06-16T10:30:00Z", - "retryCount": 0, - "maxRetries": 3 -} -``` - -### 状态机 - -``` -pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified - ↑ │ │ - └──release()─── ← (超时) ← reject() ──┘ - │ - └──fail()──→ failed -``` - -- **claim**:原子"读→找 pending→追加 claimed"操作。通过对 `/queue.lock` 加 - `flock` 防止两个进程领取同一任务。 -- **release**:将 claimed 退回 pending,重试计数 +1。 -- **fail**:当 retryCount >= maxRetries 时,标记为永久失败。 -- **超时释放**:claimed 超过 10 分钟的任务自动退回 pending。 - -### CLI 接口 - -``` -san queue list --dir [--role ] [--status ] -san queue claim --dir --role --persona -san queue add --dir --role --title "..." --description "..." -san queue complete --dir --id --persona [--pr ] [--result "..."] -san queue verify --dir --id --persona [--result "..."] -san queue release --dir --id --persona [--reason "..."] -san queue fail --dir --id --persona [--reason "..."] -``` - -注意:没有 `san queue prompt` 命令。提示词构建由 `run.sh` 完成(从认领的任务 JSON -中提取 `title` 和 `description`)。Persona 系统提示词注入由 `san --persona -p` -处理。 - -### 包结构 - -``` -internal/queue/ ← feature 层 - item.go WorkItem、ItemStatus、状态转换、ID 生成 - queue.go JSONL 存储,基于 flock 的原子追加 - claim.go Claimer:claim、release、complete、verify、fail 及超时释放 -cmd/san/queue.go cobra 子命令注册 -``` - -## 完整示例:认证功能生命周期 - -以下展示在一个真实功能实现过程中,队列项如何流经状态机。同样的示例保存在 -[`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md)。 - -### 1. Leader 创建任务(全部以 pending 开始) - -Leader 拆解"实现用户认证功能"并运行: - -```bash -san queue add --dir state/ --role dev \ - --title "定义 User 模型和 UserStore 接口" \ - --description "在 internal/core/user.go 中创建 User struct(ID, Username, PasswordHash, CreatedAt)。定义 UserStore 接口,包含 FindByUsername, Create, FindByID。" - -san queue add --dir state/ --role dev \ - --title "实现 UserStore(SQLite)" \ - --description "在 internal/feature/userstore/ 中基于 SQLite 实现 UserStore 接口。参数化查询,bcrypt 密码哈希。" - -san queue add --dir state/ --role dev \ - --title "实现 JWT token 生成与验证" \ - --description "创建 internal/core/jwt/ 包。GenerateToken 和 ValidateToken,HS256,24 小时过期。" - -san queue add --dir state/ --role dev \ - --title "实现 POST /auth/login handler" \ - --description "在 internal/app/authhandler/ 中创建登录 API handler。接收 {username, password},返回 {token}。" - -san queue add --dir state/ --role dev \ - --title "实现 Auth 中间件" \ - --description "创建中间件:提取 Bearer token、验证、注入用户信息到 context。" - -san queue add --dir state/ --role dev \ - --title "登录接口加限流" \ - --description "限制 POST /auth/login 每 IP 每分钟 5 次。" - -san queue add --dir state/ --role qe \ - --title "验证认证模块" \ - --description "检出所有认证相关 PR。运行 make test、make lint、make layercheck。在 test-integration/auth/ 下添加集成测试。" - -san queue add --dir state/ --role release \ - --title "发布 v1.3.0" \ - --description "生成 CHANGELOG,版本号升至 1.3.0,打 tag v1.3.0,生成 release notes。" -``` - -Leader 写入后的队列: - -``` -ID Role 标题 状态 负责人 -a1b2c3d4 dev 定义 User 模型和接口 pending - -b2c3d4e5 dev 实现 UserStore(SQLite) pending - -c3d4e5f6 dev 实现 JWT token 生成 pending - -d4e5f6a7 dev 实现 POST /auth/login pending - -e5f6a7b8 dev 实现 Auth 中间件 pending - -f6a7b8c9 dev 登录接口加限流 pending - -a7b8c9d0 qe 验证认证模块 pending - -b8c9d0e1 release 发布 v1.3.0 pending - -``` - -### 2. Dev 认领并实现 - -Dev 的 `run.sh` 执行:`san queue claim --dir state/ --role dev --persona dev` - -认领是原子的(flock 保护)。同一时间只有一个进程能认领同一任务。 - -``` -认领 Task 1 → status: claimed, assignedTo: dev - san --persona dev -p ".." → 实现 → PR #123 - san queue complete --id a1b2c3d4 --pr "https://github.com/genai-io/san/pull/123" - → status: done - -认领 Task 2 → status: claimed, assignedTo: dev - san --persona dev -p ".." → 实现 → PR #124 - san queue complete --id b2c3d4e5 --pr "https://github.com/genai-io/san/pull/124" - → status: done - -... (Task 3-6 同理) -``` - -### 3. QE 验证 - -QE 的 `run.sh` 轮询 `role: qe` 的任务。当所有 dev 任务 done 后: - -``` -$ san queue claim --dir state/ --role qe --persona qe -→ 认领 Task a7b8c9d0(验证认证模块),status: claimed - -$ san --persona qe -p "验证认证模块..." -→ 检出 PR #123-#126 → 运行测试 → 添加集成测试 → 通过 - -$ san queue verify --id a7b8c9d0 --persona qe --result "全部测试通过,已添加集成测试。" -→ status: verified -``` - -### 4. Release 发布 - -Release 的 `run.sh` 轮询 `role: release` 的任务: - -``` -$ san queue claim --dir state/ --role release --persona release -→ 认领 Task b8c9d0e1(发布 v1.3.0),status: claimed - -$ san --persona release -p "发布 v1.3.0..." -→ 生成 CHANGELOG → 更新版本号 → git tag v1.3.0 - -$ san queue complete --id b8c9d0e1 --persona release -→ status: done -``` - -### 5. 失败重试示例 - -``` -Task f6a7b8c9: "修复 auth 中间件的 nil pointer" - 第 1 次:claimed → agent 失败 → release → pending(retryCount: 1) - 第 2 次:claimed → agent 失败 → release → pending(retryCount: 2) - 第 3 次:claimed → agent 失败 → release → pending(retryCount: 3) - retryCount >= maxRetries → san queue fail → status: failed - -Leader 检测到失败任务,通知管理员介入。 -``` - -### 完成时的队列全景 - -```bash -$ san queue list --dir state/ -``` - -``` -ID Role 标题 状态 负责人 PR -a1b2c3d4 dev 定义 User 模型和接口 done dev #123 -b2c3d4e5 dev 实现 UserStore(SQLite) done dev #124 -c3d4e5f6 dev 实现 JWT token 生成 done dev #125 -d4e5f6a7 dev 实现 POST /auth/login done dev #126 -e5f6a7b8 dev 实现 Auth 中间件 done dev #127 -f6a7b8c9 dev 登录接口加限流 done dev #128 -a7b8c9d0 qe 验证认证模块 verified qe - -b8c9d0e1 release 发布 v1.3.0 done release - - -Total: 8 | pending: 0 | claimed: 0 | done: 7 | verified: 1 | failed: 0 -``` - -### 与 run.sh 的交互 - -完整的 `run.sh` 循环,展示队列和 Agent 如何组合: - -```bash -#!/bin/bash -set -euo pipefail -TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" -PERSONA="${1:?usage: $0 }" -CWD="${2:-$(pwd)}" -INTERVAL="${3:-30}" - -echo "[san-team:$PERSONA] 每 ${INTERVAL}s 轮询一次, cwd=$CWD" - -while true; do - TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) - if [ -n "$TASK" ]; then - ID=$(echo "$TASK" | jq -r '.id') - TITLE=$(echo "$TASK" | jq -r '.title') - DESC=$(echo "$TASK" | jq -r '.description') - echo "[san-team:$PERSONA] 认领 $ID: $TITLE" - - PROMPT="Task: $TITLE - - $DESC - - 完成后,总结你的工作内容和 PR 链接。" - - if san --persona "$PERSONA" -p "$PROMPT"; then - san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] 完成 $ID" - else - san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] 释放 $ID(将重试)" - fi - fi - sleep "$INTERVAL" -done -``` - -关键设计要点:`run.sh` 是唯一同时接触队列和 Agent 的组件。队列不知道 persona -的存在。Agent 不知道队列的存在。Shell 脚本是组合层。 - -## 影响 - -- **正面**:无数据库依赖。队列是单个 JSONL 文件,可以 `cat` 查看、`grep` 搜索、 - Git 恢复。 -- **正面**:`san queue` 独立于 san-team 可用。任何多 Agent 编排场景都可以复用。 -- **正面**:CLI 接口可脚本化——`san-team` 中的轮询循环只需 12 行 shell 脚本。 -- **正面**:清晰的职责分离。`san queue` 处理原子队列操作。`san --persona -p` - 处理带 persona 的 Agent 执行。`run.sh` 负责组合它们。 -- **负面**:JSONL 追加模式会无限增长。后续应增加 `san queue compact` 命令来压 - 缩(每个 ID 只保留最后一行)。 -- **负面**:基于文件的锁意味着所有队列操作必须在同一文件系统上。跨机器协调需要 - 共享文件系统(NFS)或通过 SSH 使用 shell 回退方案。 - -## 参考资料 - -- [ADR-0002](0002-autonomous-dev-management.md) — 自主开发管理团队 -- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — Prompt 优先的 san-team 设计 -- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — 队列示例 diff --git a/docs/design/decisions/0002-autonomous-dev-management.md b/docs/design/decisions/0002-autonomous-dev-management.md index 693910a8..5d440359 100644 --- a/docs/design/decisions/0002-autonomous-dev-management.md +++ b/docs/design/decisions/0002-autonomous-dev-management.md @@ -2,7 +2,7 @@ ## Status -Proposed — 2026-06-11. Updated 2026-06-16. +Proposed — 2026-06-11. ## Context @@ -28,13 +28,14 @@ Each Persona follows the ``` genai-io(Org, existing) ├── san ← San source repo (existing, the managed project) -└── san-team ← New repo (pure content, no Go code) +└── san-team ← New repo (San development team Personas) ├── leader/ │ ├── system/ │ │ ├── identity.md │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ + │ │ └── ... │ └── settings.json ├── dev/ │ ├── system/ @@ -42,6 +43,7 @@ genai-io(Org, existing) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ + │ │ └── ... │ └── settings.json ├── qe/ │ ├── system/ @@ -49,21 +51,22 @@ genai-io(Org, existing) │ │ ├── behavior.md │ │ └── rules.md │ ├── skills/ + │ │ └── ... │ └── settings.json - ├── release/ - │ ├── system/ - │ │ ├── identity.md - │ │ ├── behavior.md - │ │ └── rules.md - │ ├── skills/ - │ └── settings.json - ├── state/ ← queue.jsonl lives here - └── run.sh ← polling loop (12-line shell script) -``` - -- **`san-team`**: A pure-content repo — no Go code, no binaries. Contains - only persona markdown files, a shared state directory, and a shell script - for the polling loop. All Go logic lives in San as reusable features. + └── release/ + ├── system/ + │ ├── identity.md + │ ├── behavior.md + │ └── rules.md + ├── skills/ + │ └── ... + └── settings.json +``` + +- **`san-team`**: New repo containing the San project's development team. + Personas live directly under the repo root. If other projects need + similar autonomous management, they can create their own team repos + (e.g., `devops-team`). - **Team**: A collection of Personas in the `san-team` repo that collaborate toward a specific goal — managing the San project's issues, features, bugs, and releases. @@ -76,225 +79,129 @@ genai-io(Org, existing) ## Runtime Model -Each Persona runs as an **independent San instance**. The runtime model -differs by persona role: - -### Leader — Interactive TUI - -Leader is the admin's conversation partner. Runs as a standard interactive -San session with the leader persona loaded: +Each Persona runs as an **independent San instance**: ```bash -san --persona leader -``` +# Start Leader Persona (admin interaction entry point) +san start --persona leader --team san-team -This uses San's existing `--persona` flag, which loads persona config -from `~/.san/personas/leader/` (a symlink to `san-team/leader/`). +# Start Dev Persona (waits for coding tasks) +san start --persona dev --team san-team -### Dev, QE, Release — Headless via run.sh - -These personas run headlessly via `san --persona -p`, launched by -`run.sh`. They do not need interactive terminals — they poll the queue, -claim tasks, execute them, and update the queue: - -```bash -# In terminal 2: start Dev polling loop -./run.sh dev /path/to/san +# Start QE Persona (waits for verification tasks) +san start --persona qe --team san-team -# In terminal 3: start QE polling loop -./run.sh qe /path/to/san - -# In terminal 4: start Release polling loop -./run.sh release /path/to/san -``` - -`run.sh` is a simple shell script (see below). Each persona's system prompt -(identity + behavior + rules) is loaded by `san --persona -p` from -`~/.san/personas//`. - -### Setup: Linking Personas - -Before starting, link san-team personas into San's persona directory: - -```bash -ln -s /path/to/san-team/leader ~/.san/personas/leader -ln -s /path/to/san-team/dev ~/.san/personas/dev -ln -s /path/to/san-team/qe ~/.san/personas/qe -ln -s /path/to/san-team/release ~/.san/personas/release +# Start Release Persona (waits for release tasks) +san start --persona release --team san-team ``` -All personas — Leader, Dev, QE, Release — use the same `~/.san/personas/` -mechanism. No new persona-loading code is needed. +The `--persona` flag tells San to load a specific Persona directory's +config (system/ + skills/ + settings.json) at startup, avoiding the +need for mid-session `/persona` switching. -## run.sh — The Glue Layer - -`run.sh` is the only "code" in san-team. It bridges `san queue` and -`san -p`: - -```bash -#!/bin/bash -set -euo pipefail -TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" -PERSONA="${1:?usage: $0 }" -CWD="${2:-$(pwd)}" -INTERVAL="${3:-30}" - -echo "[san-team:$PERSONA] polling every ${INTERVAL}s, cwd=$CWD" - -while true; do - # 1. Atomically claim next pending task for this persona - TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) - if [ -n "$TASK" ]; then - ID=$(echo "$TASK" | jq -r '.id') - TITLE=$(echo "$TASK" | jq -r '.title') - DESC=$(echo "$TASK" | jq -r '.description') - echo "[san-team:$PERSONA] claimed $ID: $TITLE" - - # 2. Build task prompt and run agent headlessly - PROMPT="Task: $TITLE - - $DESC - - After completing, summarize what you did and any PR links." - - if san --persona "$PERSONA" -p "$PROMPT"; then - san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] completed $ID" - else - san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] released $ID (will retry)" - fi - fi - sleep "$INTERVAL" -done -``` - -Key design points: -- **Queue operations are the shell's job**: `run.sh` calls `san queue claim`, - `san queue complete`, `san queue release`. The LLM agent never knows about - the queue. -- **Persona loading is San's job**: `san --persona -p` loads the - persona system prompt from `~/.san/personas/`. The shell does not need to - compose persona files into the prompt. -- **LLM inference is San's job**: `san -p` executes the agent with - the task description as the user message. -- **The shell is just glue**: claim → run → complete/release. 12 lines. +Multiple Persona instances can run simultaneously in different +terminals, containers, or machines. They coordinate through a shared +work queue (`san-team/state/queue.jsonl`). ## Workflow The admin talks only to the Leader Persona. Leader breaks requirements -into Tasks and writes them to the shared queue via `san queue add`. -Other Personas poll the queue via `run.sh`, claim matching tasks, -complete them, and update the queue status. +into Tasks and writes them to the shared queue. Other Personas poll the +queue, claim matching tasks, complete them, and update the queue status. ``` Admin (human) │ - │ "Build user authentication" + │ "Build user authentication" or "Fix all P0 bugs" ▼ -┌────────────────────────────────────────────┐ -│ Leader (san --persona leader) │ -│ │ -│ 1. Understand intent │ -│ 2. Draw architecture & state diagrams │ -│ 3. Break down into Tasks │ -│ 4. Write tasks via: san queue add ... │ -│ 5. Monitor: san queue list ... │ -└────────────────┬───────────────────────────┘ - │ Shared work queue (state/queue.jsonl) - ┌────────┼────────┐ - ▼ ▼ ▼ - ┌──────────┐ ┌──────┐ ┌─────────┐ - │ run.sh │ │run.sh│ │ run.sh │ - │ dev │ │ qe │ │ release │ - │ │ │ │ │ │ - │ claim→ │ │claim→│ │claim→ │ - │ san -p │ │agent │ │ san -p │ - │ --persona│ │run │ │--persona│ - │ dev │ │--qe │ │ release │ - │ →complete│ │→verify│ │→complete│ - └──────────┘ └──────┘ └─────────┘ +┌──────────────────────────────────────────────────────┐ +│ Leader Persona (san start --persona leader --team san-team)│ +│ │ +│ 1. Understand intent │ +│ 2. Draw architecture & state diagrams │ +│ 3. Break down into Tasks, write to shared queue │ +│ 4. Monitor queue, collect results, report to admin │ +└──────────────────┬───────────────────────────────────┘ + │ Shared work queue (state/queue.jsonl) + ┌────────┼────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────┐ ┌─────────┐ + │Dev│ │ QE │ │ Release │ + │ san start│ │san start│ │san start│ + │ --persona│ │--persona│ │--persona│ + │dev│ │ qe │ │ release │ + └──────────┘ └──────┘ └─────────┘ ``` ### Leader Persona — Single Entry Point -Started via `san --persona leader`. The admin's only interface. Leader -handles: +Started via `san start --persona leader --team san-team`. +The admin's only interface. Leader handles: 1. **Understand intent**: new feature? bug fix? refactor? 2. **Analyze San**: read design docs and existing code in the San repo 3. **Visualize**: draw mermaid architecture/state diagrams, confirm with admin -4. **Break down**: decompose into Tasks, write to shared work queue via - `san queue add --dir state/ --role dev --title "..." --description "..."` -5. **Monitor**: track Task status via `san queue list --dir state/` +4. **Break down**: decompose into Tasks, write to shared work queue +5. **Monitor**: track Task status changes in the queue 6. **Report**: collect results, summarize for the admin Leader does not write code. It writes Tasks to the queue; the matching -Persona's `run.sh` loop picks them up automatically. +Persona's San instance picks them up automatically. ``` Leader dispatches a coding task: Leader: 1. After analysis, determines Task-3 is a coding task - 2. Runs: san queue add --dir state/ --role dev --title "Implement JWT..." - 3. Dev's run.sh polls queue, finds Task-3 matching its role - 4. Dev claims Task-3 via san queue claim, runs san --persona dev -p ".." - 5. Agent completes, run.sh calls san queue complete with PR link - 6. Leader polls queue via san queue list, sees Task-3 done + 2. Writes Task-3 to queue (marked role: dev) + 3. Dev San instance polls queue, finds Task-3 matching its role + 4. Dev claims Task-3, starts implementation + 5. On completion, updates queue status to done with PR link + 6. Leader polls queue, sees Task-3 done, proceeds to next step ``` ### Dev Persona — Implementation -Launched by `run.sh dev`. Continuously polls the queue for coding tasks: - -1. `run.sh` claims Tasks from queue with role `dev` via `san queue claim` -2. Builds prompt from task description -3. Runs `san --persona dev -p "..." -4. Agent reads San's design docs and existing code -5. Agent implements following the layered architecture conventions -6. Agent writes unit tests (in the same package) -7. Agent runs `make test` + `make lint` -8. Agent commits, creates PR -9. On success: `run.sh` calls `san queue complete --id $ID --pr ` -10. On failure: `run.sh` calls `san queue release --id $ID` +Started via `san start --persona dev --team san-team`. +Continuously polls the queue for coding tasks: -The Dev agent never touches the queue — `run.sh` handles all queue -operations based on the agent's exit code. +1. Claims Tasks from queue with role `dev` +2. Reads San's design docs and existing code +3. Implements following the layered architecture conventions +4. Writes tests +5. Runs `make test` + `make lint` +6. Commits, creates PR +7. Updates queue status to `done` with PR link ### QE Persona — Verification -Launched by `run.sh qe`. Continuously polls the queue for verification tasks: +Started via `san start --persona qe --team san-team`. +Continuously polls the queue for verification tasks: -1. `run.sh` claims Tasks from queue with role `qe` -2. Runs `san --persona qe -p "..." -3. Agent checks out the PR branch -4. Agent runs full test suite + lint + layer check -5. Agent adds integration tests in `test-integration/`, commits to same PR -6. Agent posts PR review -7. On pass: `run.sh` calls `san queue verify --id $ID --result "passed"` -8. On fail: `run.sh` calls `san queue release --id $ID --reason "..."` +1. Claims Tasks from queue with role `qe` (corresponding to Dev done) +2. Checks out the PR branch +3. Runs full test suite + lint + layer check +4. Uses `verify` skill to confirm correctness +5. Posts PR review +6. Updates queue status: `verified` or failed with reason -QE can also verify designs before implementation starts (Leader creates -a `role: qe` task with the design doc to review). +Can also verify designs before implementation starts. ### Release Persona — Shipping -Launched by `run.sh release`. Continuously polls the queue for release tasks: +Started via `san start --persona release --team san-team`: -1. `run.sh` claims Tasks from queue with role `release` -2. Runs `san --persona release -p "..." -3. Agent generates CHANGELOG -4. Agent bumps version number -5. Agent creates Git tag -6. Agent generates release notes -7. On success: `run.sh` calls `san queue complete --id $ID` +1. Claims Tasks from queue with role `release` (after all QE verified) +2. Generates CHANGELOG +3. Bumps version number +4. Creates Git tag +5. Generates release notes +6. Updates queue status to `done` ### Shared Work Queue The queue is the sole communication mechanism between Personas, stored -at `san-team/state/queue.jsonl` (JSONL append-only log). All operations -go through the `san queue` subcommand (see [ADR-0003](0003-shared-work-queue.md)). +at `san-team/state/queue.jsonl` (JSONL append-only log). ``` type WorkItem struct { @@ -308,263 +215,15 @@ type WorkItem struct { Result string // result notes (filled by QE/Release) CreatedAt time.Time UpdatedAt time.Time - ClaimedAt time.Time - RetryCount int - MaxRetries int } ``` State transitions: ``` -pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified - ↑ │ │ - └──release()─── ← (timeout) ← reject() ──┘ - │ - └──fail()──→ failed -``` - -## Complete Setup Example - -A step-by-step walkthrough from zero to running team. - -### Prerequisites - -```bash -# San CLI must be built and on PATH -cd /path/to/san -make build -export PATH="$PATH:$PWD/bin" - -# san-team repo cloned alongside san -cd /path/to -git clone git@github.com:genai-io/san-team.git -``` - -### Step 1: Link personas - -```bash -ln -s /path/to/san-team/leader ~/.san/personas/leader -ln -s /path/to/san-team/dev ~/.san/personas/dev -ln -s /path/to/san-team/qe ~/.san/personas/qe -ln -s /path/to/san-team/release ~/.san/personas/release -``` - -### Step 2: Start Leader (interactive) - -```bash -# Terminal 1: Leader session -cd /path/to/san -san --persona leader -``` - -The admin interacts with Leader here. For example: - -``` -Admin: Build user authentication with JWT login - -Leader: - Reads docs/design/ → analyzes codebase → draws sequence diagram - Confirms with admin → breaks down into tasks: - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "Define User model and UserStore interface" \ - --description "Create User struct (ID, Username, PasswordHash, CreatedAt)..." - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "Implement UserStore with SQLite" \ - --description "Implement UserStore interface using SQLite..." - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "Implement JWT token generation and verification" \ - --description "Create internal/core/jwt/ package..." - - $ san queue add --dir /path/to/san-team/state --role dev \ - --title "Implement POST /auth/login handler" \ - --description "Create login API handler..." - - $ san queue add --dir /path/to/san-team/state --role qe \ - --title "Verify auth module (tasks 1-4)" \ - --description "Check out each PR, run tests, add integration tests..." - - $ san queue add --dir /path/to/san-team/state --role release \ - --title "Ship v1.3.0 with user authentication" \ - --description "Generate CHANGELOG, bump version, tag v1.3.0..." -``` - -### Step 3: Start worker personas (headless) - -```bash -# Terminal 2: Dev polling loop -cd /path/to/san-team -./run.sh dev /path/to/san 30 - -# Terminal 3: QE polling loop -cd /path/to/san-team -./run.sh qe /path/to/san 30 - -# Terminal 4: Release polling loop -cd /path/to/san-team -./run.sh release /path/to/san 60 -``` - -### Step 4: Watch progress - -```bash -# Any terminal: check queue status -san queue list --dir /path/to/san-team/state -``` - -Output example: - -``` -ID Role Title Status Assigned PR -a1b2c3d4 dev Define User model and interface done dev #123 -b2c3d4e5 dev Implement UserStore with SQLite done dev #124 -c3d4e5f6 dev Implement JWT token generation claimed dev - -d4e5f6a7 dev Implement POST /auth/login pending - - -a7b8c9d0 qe Verify auth module (tasks 1-4) pending - - -b8c9d0e1 release Ship v1.3.0 pending - - - -Total: 6 | pending: 3 | claimed: 1 | done: 2 | verified: 0 | failed: 0 -``` - -### What happens inside run.sh - -When Dev's `run.sh` polls and finds a pending task: - -``` -1. san queue claim --dir state/ --role dev --persona dev - → Returns JSON: {"id":"d4e5f6a7...", "title":"Implement POST /auth/login", ...} - → Task status is now "claimed" - -2. san --persona dev -p ".." --prompt "Task: Implement POST /auth/login - Create login API handler at internal/app/authhandler/..." --cwd /path/to/san - → Agent loads dev persona from ~/.san/personas/dev/ - → Agent implements the handler + unit tests - → Agent creates PR #126 - → Agent exits 0 (success) - -3. san queue complete --dir state/ --id d4e5f6a7 --persona dev --pr "https://github.com/genai-io/san/pull/126" - → Task status is now "done" - -4. sleep 30, repeat -``` - -If the agent fails: - -``` -1. san queue claim ... (same) -2. san --persona dev -p "..." - → Agent hits an error, exits 1 (failure) -3. san queue release --dir state/ --id d4e5f6a7 --persona dev - → Task status reverts to "pending", retryCount++ - → Next poll will claim it again (up to maxRetries) -``` - -## Complete Workflow Example - -Admin input: **"Implement user authentication with JWT login"** - -### 1. Leader understands + draws diagrams - -Leader reads San's `docs/design/`, analyzes existing code, generates a -mermaid sequence diagram: - -```mermaid -sequenceDiagram - participant Client - participant AuthHandler - participant AuthService - participant UserStore - participant JWT - - Client->>AuthHandler: POST /auth/login - AuthHandler->>AuthService: Login(username, password) - AuthService->>UserStore: FindByUsername(username) - UserStore-->>AuthService: User - AuthService->>AuthService: Verify password - AuthService->>JWT: GenerateToken(user) - JWT-->>AuthService: token - AuthService-->>AuthHandler: token - AuthHandler-->>Client: 200 { token } -``` - -Leader shows the diagram: "This is how I understand the flow — correct?" - -### 2. Break down and write to queue - -After admin confirmation, Leader writes Tasks to the shared queue: - -``` -$ san queue add --dir state/ --role dev --title "Define User model and UserStore interface" \ - --description "Create User struct (ID, Username, PasswordHash, CreatedAt) in internal/core/user.go..." - -$ san queue add --dir state/ --role dev --title "Implement UserStore with SQLite" \ - --description "Implement UserStore interface using SQLite..." - -$ san queue add --dir state/ --role dev --title "Implement JWT token generation & verification" \ - --description "Create internal/core/jwt/ package..." - -$ san queue add --dir state/ --role dev --title "Implement login API handler" \ - --description "Create login API handler at internal/app/authhandler/..." - -$ san queue add --dir state/ --role qe --title "Verify full auth functionality" \ - --description "Check out PRs, run tests, add integration tests..." - -$ san queue add --dir state/ --role release --title "Ship v1.3.0" \ - --description "Generate CHANGELOG, bump version, tag v1.3.0..." -``` - -### 3. Personas claim and execute - -``` -Dev's run.sh polls queue: - Claims Task 1 → san --persona dev -p ".." → implements → PR #123 → complete - Claims Task 2 → san --persona dev -p ".." → implements → PR #124 → complete - Claims Task 3 → san --persona dev -p ".." → implements → PR #125 → complete - Claims Task 4 → san --persona dev -p ".." → implements → PR #126 → complete - -QE's run.sh polls queue: - Sees Tasks 1-4 done → claims Task 5 - san --persona qe -p ".." → checks out PRs → runs tests → adds integration tests - → passes → san queue verify - -Leader monitors all verified → notifies admin to approve PRs -Admin approves → Leader runs: - $ san queue add --dir state/ --role release --title "Ship v1.3.0" ... - -Release's run.sh polls queue: - Claims Task 6 → san --persona release -p ".." → changelog → tag → complete - -Leader → Admin: "Authentication feature complete and shipped. PRs: #123-#126" -``` - -## Bug Fix Flow - -Admin tells Leader: **"Scan and fix all P0 bugs"** - -``` -Leader: - 1. Pulls all P0 bug issues from San repo via gh - 2. Analyzes each, writes to queue: - $ san queue add --dir state/ --role dev --title "Fix #100 nil pointer in auth.go" ... - $ san queue add --dir state/ --role dev --title "Fix #102 timeout in db query" ... - -Dev's run.sh: - Claims "#100" → san --persona dev -p ".." → fix → PR → complete - Claims "#102" → san --persona dev -p ".." → fix → PR → complete - -QE's run.sh: - Claims "#100 verify" → san --persona qe -p ".." → verify → passes - Claims "#102 verify" → san --persona qe -p ".." → verify → passes - -Leader notifies admin to approve → admin approves - -Release's run.sh: - Claims "hotfix release" → changelog → tag → complete - -Leader → Admin: "2 P0 bugs fixed and shipped" +pending ──→ claimed ──→ done ──→ verified + │ │ + └──→ (timeout) ──→ pending (timed out, released back to queue) ``` ## Persona Configuration Examples @@ -575,7 +234,7 @@ Each Persona is a persona directory. Using Dev as an example: ```markdown You are the Dev agent for the San project. -Your job is to implement coding tasks assigned to you. +Your job is to claim coding tasks from the shared queue and implement them. You are an expert Go developer familiar with San's five-layer package architecture. ``` @@ -584,16 +243,16 @@ You are an expert Go developer familiar with San's five-layer package architectu ```markdown ## Work habits -1. Read relevant design docs and existing code first — understand context before acting -2. Follow the 5-layer architecture dependency direction: cmd → app → feature → core → infrastructure -3. Every change must include unit tests in the same package -4. Run make test and make lint after changes — only proceed if they pass -5. Commit with clear messages, create a PR, and report the PR link +1. Continuously poll the shared queue for tasks matching your role +2. Read relevant design docs and existing code first — understand context before acting +3. Follow the 5-layer architecture dependency direction: cmd → app → feature → core → infrastructure +4. Every change must include tests +5. Run make test and make lint after changes — only proceed if they pass ## Communication style -- When uncertain about a design decision, note it in your result for the Leader -- On completion, summarize what you did, which files changed, and the PR link +- When uncertain about a design decision, update the Task with your question for the Leader +- On completion, update the Task with what you did, which files changed, and the PR link - Do not go beyond the assigned Task scope ``` @@ -684,6 +343,99 @@ and decision-making. } ``` +## Complete Workflow Example + +Admin input: **"Implement user authentication with JWT login"** + +### 1. Leader understands + draws diagrams + +Leader reads San's `docs/design/`, analyzes existing code, generates a +mermaid sequence diagram: + +```mermaid +sequenceDiagram + participant Client + participant AuthHandler + participant AuthService + participant UserStore + participant JWT + + Client->>AuthHandler: POST /auth/login + AuthHandler->>AuthService: Login(username, password) + AuthService->>UserStore: FindByUsername(username) + UserStore-->>AuthService: User + AuthService->>AuthService: Verify password + AuthService->>JWT: GenerateToken(user) + JWT-->>AuthService: token + AuthService-->>AuthHandler: token + AuthHandler-->>Client: 200 { token } +``` + +Leader shows the diagram: "This is how I understand the flow — correct?" + +### 2. Break down and write to queue + +After admin confirmation, Leader writes Tasks to the shared queue: + +``` +Queue writes: + Task 1: { role: dev, title: "Define User model and UserStore interface" } + Task 2: { role: dev, title: "Implement UserStore" } + Task 3: { role: dev, title: "Implement JWT token generation & verification" } + Task 4: { role: dev, title: "Implement login API handler" } + Task 5: { role: qe, title: "Verify full auth functionality" } + Task 6: { role: release, title: "Ship v1.2.0" } +``` + +### 3. Personas claim and execute + +``` +Dev San instance polls queue: + Claims Task 1 → implements → marks done + Claims Task 2 → implements → marks done + Claims Task 3 → implements → marks done + Claims Task 4 → implements → marks done + +QE San instance polls queue: + Sees Tasks 1-4 done → claims Task 5 + Checks out PR branch → runs tests → passes → marks verified + +Leader monitors all verified → notifies admin to approve PR +Admin approves → Leader writes Task 6 + +Release San instance polls queue: + Claims Task 6 → generates CHANGELOG → tags → marks done + +Leader → Admin: "Authentication feature complete and shipped. PR: #1234" +``` + +## Bug Fix Flow + +Admin tells Leader: **"Scan and fix all P0 bugs"** + +``` +Leader: + 1. Pulls all P0 bug issues from San repo via GhCLI + 2. Analyzes each, writes to queue: + - { role: dev, title: "Fix #100 nil pointer in auth.go" } + - { role: dev, title: "Fix #102 timeout in db query" } + +Dev San instance: + Claims "#100" → analyzes root cause → fix → PR → marks done + Claims "#102" → analyzes root cause → fix → PR → marks done + +QE San instance: + Claims "#100 verify" → tests → reviews PR → passes → marks verified + Claims "#102 verify" → tests → reviews PR → passes → marks verified + +Leader notifies admin to approve → admin approves + +Release San instance: + Claims "hotfix release" → generates CHANGELOG → tags → marks done + +Leader → Admin: "2 P0 bugs fixed and shipped" +``` + ## Key Design Decisions ### 1. Persona directory @@ -700,44 +452,31 @@ Persona definitions live in a dedicated repo, separate from the San source: - Different access permissions for the san-team repo - Team Personas can manage multiple target repos (future) -### 3. san-team is pure content — no Go code - -san-team contains only markdown files, `state/`, and a shell script. All Go -logic lives in San as reusable, independently useful features: -- `san queue` — atomic work queue operations (see [ADR-0003](0003-shared-work-queue.md)) -- `san --persona -p` — headless agent with persona system prompt - -This separation means san-team has no build step, no dependencies, and can -be modified by anyone comfortable with markdown and shell scripts. - -### 4. Leader is the single entry point +### 3. Leader is the single entry point The admin never talks directly to Dev/QE/Release: - Simple mental model: one conversation partner - Leader has global visibility to prioritize and handle conflicts - Other Personas focus only on their queue tasks, don't need global context -### 5. Each Persona is an independent San instance +### 4. Each Persona is an independent San instance Instead of nested sub-agent calls via the Agent tool, each Persona runs -as an independent San process: -- **Leader**: `san --persona leader` — interactive TUI, talks to admin -- **Dev/QE/Release**: `san --persona -p` — headless, launched by `run.sh` - -Benefits: +as an independent San process (`san start --persona --team `): - Process-level isolation: each Persona has its own context, tools, permissions - Can be deployed on different machines/containers, independently scaled - Coordination through the shared work queue (file-based), no IPC needed -- All persona loading unified under `~/.san/personas/` +- Complements the `/persona` switch mechanism: `/persona` for interactive + mid-session hot-switching; `--persona` for fixed-role startup -### 6. Architecture diagrams as communication language +### 5. Architecture diagrams as communication language Leader draws mermaid diagrams before writing any code: - Admin confirms understanding (avoids building the wrong thing) - Dev Persona gets a clear reference (diagrams ship with Task descriptions) - QE Persona gets a verification checklist -### 7. Persona Self-Evolution +### 6. Persona Self-Evolution Every Persona continuously learns and self-improves during the project. Evolution is persisted by updating the Persona's configuration in the @@ -775,13 +514,11 @@ Task complete → Persona writes retrospective → identifies improvements | New Concept | Existing / Planned Mechanism | |---|---| -| Persona directory | `~/.san/personas/` (existing persona system) | -| `san --persona leader` | Existing `--persona` flag (no change) | -| `san --persona -p` | Existing: `-p` print mode with `--persona` (PR #231) | -| Shared work queue | New: `san queue` subcommand (see ADR-0003) | -| run.sh polling loop | 12-line shell script in san-team | -| Persona communication | Queue polling via `san queue`, no inter-process RPC | -| Persona permissions | settings.json permissions (existing mechanism) | +| Persona directory | Persona directory (`persona-system.md` spec) | +| `san start --persona` | Startup-time persona selection (no mid-session switch needed) | +| Shared work queue | New: filesystem JSONL queue (`state/queue.jsonl`) | +| Persona communication | Queue polling, no inter-process RPC needed | +| Persona permissions | settings.json permissions (deny: add-only) | ## Implementation Plan @@ -790,39 +527,37 @@ Task complete → Persona writes retrospective → identifies improvements - Create `san-team` repo under `genai-io` Org - Write four Persona directories (leader/dev/qe/release) - Each with `system/{identity,behavior,rules}.md` + `skills/` + `settings.json` -- Create `run.sh` polling script -- Create `state/` directory (queue.jsonl created at runtime) -### Phase 2 — Add `san queue` subcommand to San +### Phase 2 — `san start --persona` feature -- Implement `internal/queue/` package (item, queue, claim) — ~200 lines -- Wire `cmd/san/queue.go` cobra subcommand -- Commands: list, claim, add, complete, verify, release, fail -- See [ADR-0003](0003-shared-work-queue.md) for details +- San CLI adds `--persona` and `--team` flags +- On startup, load the specified Persona config from the team directory +- Load system/ files as the system prompt +- Load skills/ as active skills +- Apply settings.json permissions and model config -### Phase 3 — `--persona` + `-p` integration (done, PR #231) +### Phase 3 — Shared work queue -- Fix `runPrint` to load persona system prompt when `--persona` and `-p` are - combined -- Load persona from `~/.san/personas//` and build system prompt via - `system.Build()` with persona identity/behavior/rules -- Apply persona's settings.json model and disabledTools -- This replaces the need for `run.sh` to compose persona files into the prompt +- Implement JSONL persistent queue (`state/queue.jsonl`) +- Queue write (Leader creates Tasks) +- Queue poll (Personas claim tasks matching their role) +- State transitions (pending → claimed → done → verified) +- Timeout release (claimed tasks auto-revert to pending after timeout) ### Phase 4 — End-to-end workflows -- Leader: `san --persona leader` → understand → break down → `san queue add` -- Dev: `run.sh dev` → `san queue claim` → `san --persona dev -p ".."` → `san queue complete` -- QE: `run.sh qe` → `san queue claim` → `san --persona qe -p ".."` → `san queue verify` -- Release: `run.sh release` → `san queue claim` → `san --persona release -p ".."` → `san queue complete` -- Leader: `san queue list` → monitor → report to admin +- Leader: design doc → break down → write to queue +- Dev Persona: poll → claim → implement → commit → update queue +- QE Persona: poll → claim → verify → update queue +- Release Persona: poll → claim → ship → update queue +- Leader: monitor queue status → report to admin ### Phase 5 — Automation and operations - Cron-triggered bug scanning → auto-write to queue - Auto-trigger Leader task breakdown on design doc merge -- Progress dashboard (`san queue list`) -- `san queue compact` for JSONL compaction +- Progress dashboard (CLI: `san team status`) +- Persona instance health monitoring ## Reference Skills @@ -881,9 +616,8 @@ directly adopt them or use them as design references. before merging — no automatic merge. After QE passes, Leader notifies the admin to approve; the admin just clicks approve. 3. **Failure retry strategy**: The Leader decides the max retry count per Task - (default 3). When Dev exhausts retries and still fails QE, `run.sh` calls - `san queue fail`, marking the Task as `failed`. Leader detects this and - notifies the admin. + (default 3). When Dev exhausts retries and still fails QE, Leader + marks the Task as `failed`, records the reason, and notifies the admin. 4. **Crash recovery**: All failure scenarios (Persona crash, Leader crash, queue file corruption, etc.) must be handled gracefully and reported to the admin: @@ -891,17 +625,12 @@ directly adopt them or use them as design references. detects the reversion and notifies the admin - Leader crash: admin restarts Leader, which replays the queue to restore context - Queue file corruption: recover the last good snapshot from Git history - - `run.sh` crash: simply restart the script; it picks up where it left off - (any claimed task auto-reverts after timeout) 5. **Future team repos**: Beyond `san-team`, what infrastructure will teams like `devops-team` need? ## References - [`persona-system.md`](../../notes/active/persona-system.md) — persona design spec that Persona directories follow -- [ADR-0003](0003-shared-work-queue.md) — shared work queue design (`san queue`) -- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — prompt-first san-team design -- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — queue examples - [`core.Agent`](../../packages/core.md) — underlying agent primitive - [`packages/subagent.md`](../../packages/subagent.md) — sub-agent mechanism - [`packages/skill.md`](../../packages/skill.md) — skill loading diff --git a/docs/design/decisions/0003-shared-work-queue.md b/docs/design/decisions/0003-shared-work-queue.md deleted file mode 100644 index 2a3d6ce4..00000000 --- a/docs/design/decisions/0003-shared-work-queue.md +++ /dev/null @@ -1,325 +0,0 @@ -# ADR-0003: Shared Work Queue for Multi-Persona Coordination - -## Status - -Proposed — 2026-06-16. - -## Context - -ADR-0002 defines an autonomous development management model where multiple -personas (Leader, Dev, QE, Release) run as independent San instances and -coordinate through a shared work queue. - -The queue must: -- Allow multiple independent processes to safely claim and update tasks - without a central server or database -- Be human-readable and git-versionable for crash recovery -- Support a task lifecycle state machine: pending → claimed → done → verified -- Work with shell scripts (`run.sh` polling loop) without requiring a Go binary - in the team repo - -## Decision - -Implement a **file-based JSONL work queue** served by a new `san queue` -subcommand in the San CLI. - -### Relationship to Persona System - -`san queue` and `san --persona -p` are independent, composable features: - -- **`san queue`** manages the shared work queue: add, claim, complete, verify, - release, fail, list. It knows nothing about personas or LLM agents. -- **`san --persona -p`** loads a persona from `~/.san/personas/`, - injects its system prompt (identity + behavior + rules), and runs a headless - agent. It knows nothing about the queue. - -The `run.sh` script in san-team composes them: - -``` -run.sh → san queue claim → san --persona -p ".."→ san queue complete/release -``` - -This separation means `san queue` is independently useful beyond san-team — -any multi-agent orchestration scenario, CI pipeline, or task scheduler can -use it. - -### Storage format - -``` -/state/queue.jsonl -``` - -Each line is a compact JSON object. Status changes append new lines (same ID, -updated fields). Readers deduplicate by ID, keeping the latest line. This -makes the queue trivially recoverable from Git history (`git checkout -- -state/queue.jsonl`). - -### Work item schema - -```json -{ - "id": "a1b2c3d4e5f6a7b8", - "role": "dev", - "title": "Implement JWT token generation", - "description": "Create internal/core/jwt/ package...", - "status": "claimed", - "assignedTo": "dev", - "pr": "", - "result": "", - "createdAt": "2026-06-16T10:00:00Z", - "updatedAt": "2026-06-16T10:30:00Z", - "claimedAt": "2026-06-16T10:30:00Z", - "retryCount": 0, - "maxRetries": 3 -} -``` - -### State machine - -``` -pending ──claim()──→ claimed ──complete()──→ done ──verify()──→ verified - ↑ │ │ - └──release()─── ← (timeout) ← reject() ──┘ - │ - └──fail()──→ failed -``` - -- `claim`: atomic read→find pending→append claimed operation. Uses `flock` on - `/queue.lock` to prevent two processes claiming the same item. -- `release`: reverts claimed→pending, bumps retryCount. -- `fail`: when retryCount >= maxRetries, mark as permanently failed. -- Timeout release: claimed items older than 10 minutes auto-revert to pending. - -### CLI surface - -``` -san queue list --dir [--role ] [--status ] -san queue claim --dir --role --persona -san queue add --dir --role --title "..." --description "..." -san queue complete --dir --id --persona [--pr ] [--result "..."] -san queue verify --dir --id --persona [--result "..."] -san queue release --dir --id --persona [--reason "..."] -san queue fail --dir --id --persona [--reason "..."] -``` - -Note: there is no `san queue prompt` command. Prompt composition is done by -`run.sh` (extracting `title` and `description` from the claimed task JSON). -Persona system prompt injection is handled by `san --persona -p`. - -### Package structure - -``` -internal/queue/ ← feature layer - item.go WorkItem, ItemStatus, state transitions, ID generation - queue.go JSONLStore with flock-based atomic append - claim.go Claimer: claim, release, complete, verify, fail + stale release -cmd/san/queue.go Cobra subcommand wiring -``` - -## Complete Example: Authentication Feature Lifecycle - -This shows how queue items flow through the state machine during a real -feature implementation. The same example lives at -[`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md). - -### 1. Leader creates tasks (all start as pending) - -Leader breaks down "Build user authentication" and runs: - -```bash -san queue add --dir state/ --role dev \ - --title "Define User model and UserStore interface" \ - --description "Create User struct (ID, Username, PasswordHash, CreatedAt) in internal/core/user.go. Define UserStore interface with FindByUsername, Create, FindByID methods." - -san queue add --dir state/ --role dev \ - --title "Implement UserStore with SQLite" \ - --description "Implement UserStore interface using SQLite in internal/feature/userstore/. Parameterized queries, bcrypt password hashing." - -san queue add --dir state/ --role dev \ - --title "Implement JWT token generation and verification" \ - --description "Create internal/core/jwt/ package with GenerateToken and ValidateToken. HS256, 24h expiry." - -san queue add --dir state/ --role dev \ - --title "Implement POST /auth/login handler" \ - --description "Create login API handler at internal/app/authhandler/. Accept {username, password}, return {token}." - -san queue add --dir state/ --role dev \ - --title "Implement auth middleware" \ - --description "Create middleware that extracts Bearer token, validates, injects user info into context." - -san queue add --dir state/ --role dev \ - --title "Add rate limiting to login endpoint" \ - --description "Limit POST /auth/login to 5 attempts per minute per IP." - -san queue add --dir state/ --role qe \ - --title "Verify auth module" \ - --description "Check out all auth PRs. Run make test, make lint, make layercheck. Add integration tests under test-integration/auth/." - -san queue add --dir state/ --role release \ - --title "Ship v1.3.0 with user authentication" \ - --description "Generate CHANGELOG, bump version to 1.3.0, tag v1.3.0, generate release notes." -``` - -Queue after Leader's work: - -``` -ID Role Title Status Assigned -a1b2c3d4 dev Define User model and interface pending - -b2c3d4e5 dev Implement UserStore with SQLite pending - -c3d4e5f6 dev Implement JWT token generation pending - -d4e5f6a7 dev Implement POST /auth/login pending - -e5f6a7b8 dev Implement auth middleware pending - -f6a7b8c9 dev Add rate limiting to login pending - -a7b8c9d0 qe Verify auth module pending - -b8c9d0e1 release Ship v1.3.0 pending - -``` - -### 2. Dev claims and implements - -Dev's `run.sh` runs: `san queue claim --dir state/ --role dev --persona dev` - -Claim is atomic (flock-protected). Only one process can claim a given item. - -``` -Claim Task 1 → status: claimed, assignedTo: dev - san --persona dev -p ".." → implements → PR #123 - san queue complete --id a1b2c3d4 --pr "https://github.com/genai-io/san/pull/123" - → status: done - -Claim Task 2 → status: claimed, assignedTo: dev - san --persona dev -p ".." → implements → PR #124 - san queue complete --id b2c3d4e5 --pr "https://github.com/genai-io/san/pull/124" - → status: done - -... (Tasks 3-6 similarly) -``` - -### 3. QE verifies - -QE's `run.sh` polls for `role: qe` tasks. When all dev tasks are done: - -``` -$ san queue claim --dir state/ --role qe --persona qe -→ Claims Task a7b8c9d0 (Verify auth module), status: claimed - -$ san --persona qe -p "Verify auth module..." -→ Checks out PRs #123-#126 → runs tests → adds integration tests → passes - -$ san queue verify --id a7b8c9d0 --persona qe --result "All tests pass. Integration tests added." -→ status: verified -``` - -### 4. Release ships - -Release's `run.sh` polls for `role: release` tasks: - -``` -$ san queue claim --dir state/ --role release --persona release -→ Claims Task b8c9d0e1 (Ship v1.3.0), status: claimed - -$ san --persona release -p "Ship v1.3.0..." -→ Generates CHANGELOG → bumps version → git tag v1.3.0 - -$ san queue complete --id b8c9d0e1 --persona release -→ status: done -``` - -### 5. Failure and retry example - -``` -Task f6a7b8c9: "Fix nil pointer in auth middleware" - Attempt 1: claimed → agent fails → release → pending (retryCount: 1) - Attempt 2: claimed → agent fails → release → pending (retryCount: 2) - Attempt 3: claimed → agent fails → release → pending (retryCount: 3) - retryCount >= maxRetries → san queue fail → status: failed - -Leader detects failed task and notifies admin. -``` - -### Full queue state at completion - -```bash -$ san queue list --dir state/ -``` - -``` -ID Role Title Status Assigned PR -a1b2c3d4 dev Define User model and interface done dev #123 -b2c3d4e5 dev Implement UserStore with SQLite done dev #124 -c3d4e5f6 dev Implement JWT token generation done dev #125 -d4e5f6a7 dev Implement POST /auth/login done dev #126 -e5f6a7b8 dev Implement auth middleware done dev #127 -f6a7b8c9 dev Add rate limiting to login done dev #128 -a7b8c9d0 qe Verify auth module verified qe - -b8c9d0e1 release Ship v1.3.0 done release - - -Total: 8 | pending: 0 | claimed: 0 | done: 7 | verified: 1 | failed: 0 -``` - -### Interaction with run.sh - -The complete `run.sh` loop showing how queue and agent compose: - -```bash -#!/bin/bash -set -euo pipefail -TEAM_DIR="$(cd "$(dirname "$0")" && pwd)" -PERSONA="${1:?usage: $0 }" -CWD="${2:-$(pwd)}" -INTERVAL="${3:-30}" - -echo "[san-team:$PERSONA] polling every ${INTERVAL}s, cwd=$CWD" - -while true; do - TASK=$(san queue claim --dir "$TEAM_DIR/state" --role "$PERSONA" --persona "$PERSONA" 2>/dev/null || true) - if [ -n "$TASK" ]; then - ID=$(echo "$TASK" | jq -r '.id') - TITLE=$(echo "$TASK" | jq -r '.title') - DESC=$(echo "$TASK" | jq -r '.description') - echo "[san-team:$PERSONA] claimed $ID: $TITLE" - - PROMPT="Task: $TITLE - - $DESC - - After completing, summarize what you did and any PR links." - - if san --persona "$PERSONA" -p "$PROMPT"; then - san queue complete --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] completed $ID" - else - san queue release --dir "$TEAM_DIR/state" --id "$ID" --persona "$PERSONA" - echo "[san-team:$PERSONA] released $ID (will retry)" - fi - fi - sleep "$INTERVAL" -done -``` - -Key design insight: `run.sh` is the only component that touches both the -queue and the agent. The queue doesn't know about personas. The agent -doesn't know about the queue. The shell script is the composition layer. - -## Consequences - -- **Positive**: No database dependency. The queue is a single JSONL file that - can be inspected with `cat`, searched with `grep`, and recovered from Git. -- **Positive**: `san queue` is independently useful beyond san-team. Any - multi-agent orchestration scenario can use it. -- **Positive**: The CLI surface is scriptable — the polling loop in `san-team` - is a 12-line shell script. -- **Positive**: Clean separation of concerns. `san queue` handles atomic queue - operations. `san --persona -p` handles persona-aware agent execution. - `run.sh` composes them. -- **Negative**: JSONL append-only grows unboundedly. Periodic compaction (keep - only the latest line per ID) should be added as a `san queue compact` command - later. -- **Negative**: File-based locking means all queue operations must be on the - same filesystem. Cross-machine coordination requires a shared filesystem (NFS) - or the shell fallback over SSH. - -## References - -- [ADR-0002](0002-autonomous-dev-management.md) — autonomous development management team -- [`san-team/DESIGN.md`](../../../san-team/DESIGN.md) — prompt-first san-team design -- [`san-team/state/EXAMPLE.md`](../../../san-team/state/EXAMPLE.md) — queue examples From 46849043db629aac7c0d6ca63d1ecfdb01f2710e Mon Sep 17 00:00:00 2001 From: dangpeng Date: Fri, 19 Jun 2026 10:46:43 +0800 Subject: [PATCH 4/4] docs(design): revise ADR-0004 from built-in to persona folder distribution Shift the readonly persona from an embedded binary approach to a standalone persona folder published at github.com/genai-io/readonly-persona. Users install via git clone into ~/.san/personas/readonly/. Co-Authored-By: Claude Opus 4.7 Signed-off-by: dangpeng --- .../0004-default-readonly-persona.md | 63 +++++++++------- .../0004-default-readonly-persona.md | 74 ++++++++++++------- 2 files changed, 84 insertions(+), 53 deletions(-) diff --git a/docs/cn/design/decisions/0004-default-readonly-persona.md b/docs/cn/design/decisions/0004-default-readonly-persona.md index 5628e22a..8cf0ca0a 100644 --- a/docs/cn/design/decisions/0004-default-readonly-persona.md +++ b/docs/cn/design/decisions/0004-default-readonly-persona.md @@ -13,7 +13,7 @@ San 目前没有默认 Persona。当没有明确选择 Persona 时,系统提 "解释这段代码"、"这个测试为什么失败"、"这个包是做什么的" —— 本不需要写权限, 但用户却得到了全部权限。 -目标是提供一个内置的只读 Persona,具有两个核心优势: +目标是提供一个只读 Persona,具有两个核心优势: 1. **环境保护**:Persona 在物理上无法修改用户的工作环境 —— 不能写文件、不能变更 Git 状态、不能安装包。这消除了意外或幻觉产生的破坏性操作 @@ -26,21 +26,25 @@ San 目前没有默认 Persona。当没有明确选择 Persona 时,系统提 ## 核心模型 -一个**内置于 San 二进制文件中的 "readonly" Persona**。与早期草案中回退到 +一个**"readonly" Persona**,以标准 Persona 目录形式分发。与早期草案中回退到 San 默认 `behavior.md` 不同,此 Persona **覆盖全部三个散文部分**, 每个部分都采用最简化、针对只读场景的内容。San 默认的工程提示词中 没有任何内容残留 —— 模型只看到只读助手所需的内容。 ``` -内嵌于二进制文件: - readonly/ - ├── system/ - │ ├── identity.md ← 最简:"你是一个只读助手" - │ ├── behavior.md ← 最简:如何分析、解释、调试 - │ └── rules.md ← 最简:只读约束 + 被要求写操作时的回应方式 - └── settings.json ← permissions.deny 阻止写工具(强制执行层) +.san/personas/readonly/ +├── system/ +│ ├── identity.md ← 最简:"你是一个只读助手" +│ ├── behavior.md ← 最简:如何分析、解释、调试 +│ └── rules.md ← 最简:只读约束 + 被要求写操作时的回应方式 +└── settings.json ← permissions.deny 阻止写工具(强制执行层) ``` +该 Persona 是普通的 Persona 目录 —— 无需任何引擎改动。它发布在独立仓库 +[github.com/genai-io/readonly-persona](https://github.com/genai-io/readonly-persona), +用户通过 `git clone` 安装到 `~/.san/personas/readonly/`。主仓库可以包含 +轻量引用或示例来展示模式并链接到独立仓库,但不负责维护完整的 Persona。 + 每个部分都刻意精简。设计原则:**如果一句话无助于模型阅读、分析或解释,就删掉。** ### Prompt 内容 @@ -155,15 +159,20 @@ San 内置默认值与只读替代内容的行数对比: ## 决策 -### D1:内置于二进制文件,而非磁盘文件 +### D1:以 Persona 目录形式分发,而非内置于二进制文件 -Readonly Persona 通过 `embed.FS` 编译进 San 二进制文件,而非从 -`~/.san/personas/` 或 `.san/personas/` 扫描。即使全新安装、没有任何 -Persona 目录,该 Persona 也始终可用。 +Readonly Persona 是标准的 Persona 目录 —— 与任何用户创建的 Persona 格式完全一致。 +无需引擎改动、无需 `embed.FS`、无需特殊加载路径。现有 Persona 系统已支持其全部需求: +`system/{identity,behavior,rules}.md` 加上一个包含 `permissions.deny` 的 `settings.json`。 -它仍然注册在 Persona 注册表中,因此 `/persona readonly` 可以正常切换, +它发布在 [github.com/genai-io/readonly-persona](https://github.com/genai-io/readonly-persona)。 +用户通过 `git clone` 安装到 `~/.san/personas/readonly/` 或 +`.san/personas/readonly/`。安装后 `/persona readonly` 即可正常切换, 并在 `/persona` 选择器中可见。 +San 主仓库可以包含轻量引用(README 指针或最小示例)来展示模式及链接到独立仓库, +但不负责维护完整的 Persona。 + ### D2:暂不作为系统默认值 Readonly Persona **默认可用**,但未选择 Persona 时的系统默认行为暂时不变 —— @@ -273,12 +282,12 @@ fi ## 实现计划 -### 阶段一:内嵌 Persona 文件 +### 阶段一:Persona 目录 -创建 `internal/persona/builtin/readonly/`: +创建 Persona 目录: ``` -internal/persona/builtin/readonly/ +.san/personas/readonly/ ├── system/ │ ├── identity.md │ ├── behavior.md @@ -286,18 +295,22 @@ internal/persona/builtin/readonly/ └── settings.json ``` -通过 `embed.FS` 内嵌。在 Persona 注册表中注册为内置条目,不可被同名磁盘 Persona 覆盖。 +按上文 [Prompt 内容](#prompt-内容) 编写三个 prompt 文件(`identity.md`、`behavior.md`、 +`rules.md`),以及包含 [D4](#d4双层防御--permissionsdeny强制--rulesmd建议) 中 deny 列表的 `settings.json`。 -### 阶段二:`/persona` 集成 +### 阶段二:发布为独立仓库 -- 在 `/persona` 选择器中展示 "readonly" 选项。 -- 支持 `/persona readonly` 切换。 -- 持久化选择,使 `persona: readonly` 配置在重启后仍然生效。 +已发布在 [github.com/genai-io/readonly-persona](https://github.com/genai-io/readonly-persona)。 +用户通过以下命令安装: + +```bash +git clone https://github.com/genai-io/readonly-persona.git ~/.san/personas/readonly +``` -### 阶段三:将 Prompt 接入 Persona 加载器 +### 阶段三:主仓库引用(可选) -按上文 [Prompt 内容](#prompt-内容) 定义的三个文件(`identity.md`、`behavior.md`、 -`rules.md`)编写并与 `settings.json` 一并内嵌。 +可选择在 San 主仓库中添加指针或最小示例,以展示 Persona 模式并链接到独立仓库 —— +不将完整 Persona 的维护责任纳入核心。 ## 影响 diff --git a/docs/design/decisions/0004-default-readonly-persona.md b/docs/design/decisions/0004-default-readonly-persona.md index 39b3f7aa..6e7f8e94 100644 --- a/docs/design/decisions/0004-default-readonly-persona.md +++ b/docs/design/decisions/0004-default-readonly-persona.md @@ -14,7 +14,7 @@ This means a fresh San session has no read-only guardrails. Common starting workflows — "explain this code," "why is this test failing," "what does this package do" — don't need write access, but the user gets it anyway. -The goal is to ship a built-in readonly persona with two core benefits: +The goal is to ship a readonly persona with two core benefits: 1. **Environment protection**: The persona physically cannot modify the user's working environment — no file writes, no git mutations, no package installs. @@ -30,22 +30,27 @@ The goal is to ship a built-in readonly persona with two core benefits: ## Core Model -A **built-in "readonly" persona** embedded in the San binary. Unlike the +A **"readonly" persona** distributed as a standard persona folder. Unlike the earlier draft that fell back to San's default `behavior.md`, this persona **overrides all three prose parts** with minimal, read-only-specific content. Nothing from San's default engineering prompt survives — the model only sees what a read-only assistant needs. ``` -embedded in binary: - readonly/ - ├── system/ - │ ├── identity.md ← minimal: "You are a read-only assistant" - │ ├── behavior.md ← minimal: how to analyze, explain, debug - │ └── rules.md ← minimal: read-only constraint + what to do when asked to write - └── settings.json ← permissions.deny for write tools (enforcement layer) +.san/personas/readonly/ +├── system/ +│ ├── identity.md ← minimal: "You are a read-only assistant" +│ ├── behavior.md ← minimal: how to analyze, explain, debug +│ └── rules.md ← minimal: read-only constraint + what to do when asked to write +└── settings.json ← permissions.deny for write tools (enforcement layer) ``` +The persona is a regular persona directory — no engine changes required. It is +published at [github.com/genai-io/readonly-persona](https://github.com/genai-io/readonly-persona). +Users install it by cloning into `~/.san/personas/readonly/`. The main repo may +carry a lightweight reference to demonstrate the pattern and link to the +standalone repo, but does not own or maintain the full persona. + Each part is intentionally short. The design principle is: **if a sentence does not help the model read, analyze, or explain, cut it.** @@ -168,14 +173,23 @@ prompt-cache prefix, the smaller prompt also means a smaller cache entry. ## Decision -### D1: Ship as built-in, not on-disk +### D1: Ship as a persona folder, not built into the binary -The readonly persona is compiled into the San binary via `embed.FS`, not -scanned from `~/.san/personas/` or `.san/personas/`. It is always available -even on a fresh install with no persona directories. +The readonly persona is a standard persona directory — the same shape as any +user-created persona. No engine changes, no `embed.FS`, no special loading +path. The existing persona system already supports everything it needs: +`system/{identity,behavior,rules}.md` plus a `settings.json` with +`permissions.deny`. -It is still registered in the persona registry so `/persona readonly` works -and it appears in the `/persona` selector. +It is published at +[github.com/genai-io/readonly-persona](https://github.com/genai-io/readonly-persona). +Users install it by cloning into `~/.san/personas/readonly/` or +`.san/personas/readonly/`. Once installed, `/persona readonly` works and it +appears in the `/persona` selector. + +The main San repo may carry a lightweight reference (a README pointer or a +minimal example) to demonstrate the pattern and link to the standalone repo, +but does not own or maintain the full persona. ### D2: Not the system default (initially) @@ -297,12 +311,12 @@ effective settings overlay. No special subagent handling is needed. ## Implementation -### Phase 1 — Embedded persona files +### Phase 1 — Persona folder -Create `internal/persona/builtin/readonly/`: +Create the persona directory: ``` -internal/persona/builtin/readonly/ +.san/personas/readonly/ ├── system/ │ ├── identity.md │ ├── behavior.md @@ -310,20 +324,24 @@ internal/persona/builtin/readonly/ └── settings.json ``` -Embed via `embed.FS`. Register in the persona registry as a built-in entry -that cannot be overridden by on-disk personas of the same name. +Write the three prompt files (`identity.md`, `behavior.md`, `rules.md`) as +defined in [Prompt content](#prompt-content) above, and `settings.json` with +the deny list from [D4](#d4-enforcement--permissionsdeny-primary--rulesmd-advisory). + +### Phase 2 — Publish as standalone repo -### Phase 2 — `/persona` integration +Published at [github.com/genai-io/readonly-persona](https://github.com/genai-io/readonly-persona). +Users install via: -- Show "readonly" in the `/persona` selector. -- Support `/persona readonly` to switch. -- Persist the selection so `persona: readonly` in settings survives restart. +```bash +git clone https://github.com/genai-io/readonly-persona.git ~/.san/personas/readonly +``` -### Phase 3 — Wire prompts into the persona loader +### Phase 3 — In-repo reference (optional) -Write the three prompt files (`identity.md`, `behavior.md`, `rules.md`) as -defined in [Prompt content](#prompt-content) above and embed them alongside -`settings.json`. +Optionally add a pointer or minimal example in the main San repo to +demonstrate the persona pattern and link to the standalone repo — without +committing core to maintaining the full persona. ## Consequences