diff --git a/DESIGN.md b/DESIGN.md index 2a08315..d9aca43 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,520 +1,351 @@ # 设计文档 -> 一个本地运行的个人任务 agent。覆盖三类工作:写汇报 PPT、写科研申报书、写代码。 -> 模型自由(LiteLLM 接 OpenAI-compatible),代码可控(目标 1500-2000 行 Python,自己读得懂)。 +> 本地运行的个人任务 agent,覆盖三类工作:汇报 PPT、科研申报书、代码。 +> 模型自由(LiteLLM 接 OpenAI-compatible),代码可控(目标 1500-2000 行 Python)。 --- ## 1. 边界 -### 做什么 -- **PPT**:文本 / 会议纪要 → `.pptx`(用 `python-pptx`) -- **科研申报**:课题信息 → 分章节 `.docx`(用 `python-docx`) -- **编码**:文件编辑、shell 执行、迭代验证 +**做**:PPT(`python-pptx`)/ 申报书(`python-docx`)/ 编码(读写文件 + shell + 迭代验证)。 +**不做**:子 agent / IM 渠道 / 自定义 RAG / 锁定 Anthropic / Eval Suite(个人工具 dogfooding 替代)。多用户 / Web UI 归 §7。 -### 不做什么 -- 子 agent / IM 渠道 / 自定义 RAG / 锁定 Anthropic(注:多用户 / Web UI 是 §7 SaaS 化路线,personal-tool 阶段不做) -- **Eval Suite**:个人工具用 dogfooding 判断模型升级,造作 case 没区分度 - -### 关键约束 -- 模型自由:LiteLLM 接 OpenAI-compatible 任意 provider(默认 DeepSeek V4) +**关键约束**: +- 模型自由:LiteLLM + OpenAI-compatible(默认 DeepSeek V4) - 任务持久化:任意时刻关机,下次能恢复 -- 演化性:模型升级时 agent 跟着升级,不需要大改架构 -- **形态兼容**:本地 CLI 与 SaaS 共享同一份 core 和同一种 storage(PG,无 SQLite / JSON 分支);CLI 长期保留(本地直跑 + `--remote` API client 双模式),不会被 HTTP API 取代(详 §7.0) +- 演化性:模型升级不需要大改架构 +- **形态兼容**:本地 CLI 与 SaaS 共享同一份 core 和 storage(PG,无 SQLite / JSON 分支);CLI 长期保留(本地直跑 + `--remote` API client 双模式) --- ## 2. 架构 -### 目录树(实际) - ``` zcbot/ ├── core/ │ ├── capabilities.py # ModelCapabilities,从 yaml 加载 -│ ├── llm.py # LiteLLM 封装,按 capabilities 自动启用 features +│ ├── llm.py # LiteLLM 封装,按 capabilities 自动启 features │ ├── loop.py # ReAct 主循环 │ ├── probe.py # 真实探测对账 yaml 声称的能力 -│ ├── session.py # 消息列表 + meta + 落盘 messages.json -│ ├── skills.py # SkillRegistry (Anthropic 渐进披露格式) -│ └── task.py # TaskState (mode/desc/status/tokens/timestamps) +│ ├── session.py # 消息列表 + meta + 落盘 +│ ├── skills.py # SkillRegistry (Anthropic 渐进披露) +│ └── task.py # TaskState ├── tools/ -│ ├── base.py # Tool 基类 + _resolve 路径 +│ ├── base.py # Tool 基类 + _resolve │ ├── fs.py # read / write / edit (唯一匹配) / glob / grep │ ├── shell.py # subprocess + 黑名单 │ ├── run_python.py # tmp .py + subprocess,过滤敏感 env │ └── skill_tool.py # load_skill -├── skills/ -│ ├── coding/ # SKILL.md -│ ├── ppt/ # SKILL.md + references/ + scripts/ + assets/ -│ └── proposal/ # SKILL.md -├── prompts/system/ -│ └── general_v1.md -├── config/ -│ ├── agent.yaml -│ └── models/ -│ └── deepseek_v4.yaml # flash + pro 两档 +├── skills/{coding,ppt,proposal}/ # SKILL.md + references / scripts / assets +├── prompts/system/general_v1.md +├── config/{agent.yaml, models/deepseek_v4.yaml} ├── workspace/ -│ ├── memory/ # 双层记忆 (workspace 级,跨 task 共享) -│ │ ├── core.md # 注 system prompt,常驻 -│ │ └── extended/ # 索引(标题+绝对路径)注 prompt,内容靠 read 工具按需拉 -│ │ └── *.md -│ └── tasks// # task_dir:仅 skill 产物,state/messages 在 PG -│ ├── spec_lock.md # skill 阶段一产物 (proposal/ppt) -│ ├── source/ # proposal 用户素材 (PDF / 团队介绍) -│ ├── source.md # ppt 转过的素材 -│ ├── sections/ # proposal 逐章 md (01_summary.md ... 12_appendix.md) -│ ├── slides/ # ppt 中间素材 (chart_p?.png) -│ └── .docx / .pptx # 最终产物 -├── main.py # 装配 (build_agent) -└── cli.py # CLI: chat / tasks / probe +│ ├── memory/{core.md, extended/*.md} # 跨 task 共享记忆 +│ └── tasks// # task_dir:仅 skill 产物,state/messages 在 PG +└── {main.py, cli.py} ``` -**task_dir = `workspace/tasks//`,所有 skill 产物都写到这里**。task_dir 绝对路径在 system prompt 里显式给 agent,SKILL.md 的 `` 占位符指向它。如果 agent 写错位置(写到 cwd / `skills/` / repo 根),git status 会立刻报红 —— `.gitignore` 不再用无锚通配规则盖住污染。 +**task_dir = `workspace/tasks//`,所有 skill 产物写到这里**,绝对路径在 system prompt 显式给 agent。写错位置(cwd / `skills/` / repo 根)git status 立刻报红,不再用无锚 .gitignore 通配盖污染。 -### 启动时拼装顺序 -1. 读 `config/agent.yaml` 拿 default_model;`ZCBOT_DB_URL` 环境变量指向 PG(本地 dev 连远端测试 PG 或 docker compose 起的本地 PG;两形态同一种 schema) -2. `ModelCapabilities.load("deepseek_v4.flash", config/models/)` 拿能力档案 -3. `LLM(caps)` 构造,从 env 读 API key -4. 解析 task_dir(新建 or resume) -5. 拼 system prompt:`prompts/system/general_v1.md` + `SkillRegistry.discovery_block()`(skill 列表)+ cwd + **task_dir 绝对路径**(产物根) -6. 装配工具集(fs / shell / load_skill / run_python) -7. 启动 REPL —— **新建路径不预占文件**(懒创建,见 §3.6) +**启动**:读 `agent.yaml` → 加载 `ModelCapabilities` → `LLM(caps)` → 解析 task_dir → 拼 system prompt(general_v1.md + skill discovery + cwd + task_dir 绝对路径)→ 装配工具 → REPL。新建路径**懒创建**,不预占文件(§3.6)。`ZCBOT_DB_URL` 指 PG(本地 docker compose / 远端 dev / 生产)。 --- ## 3. 核心组件 ### 3.1 主循环(`core/loop.py`) -ReAct 风格:LLM → 若有 tool_calls 就执行 → 把结果塞回消息列表 → 再调 LLM。无 tool_call 即返回。 -- 工具结果对模型截断到 16K 字符,对用户预览 400 字符 -- thinking spinner 由后台 daemon 线程每 100ms 刷新文本:`thinking... 1.3s ctx 12,345 tok`(累计 token 反映上下文大小) -- 每轮 LLM 返回追加 dim 一行 `[in N out N t Xs]` —— 留痕本轮成本 -- assistant 文字走 `rich.markdown.Markdown` 渲染,粗体/列表/表格/代码块正常展示(非流式,整段渲染) -- `max_iterations` 从 capabilities 读,不同模型不同 +ReAct:LLM → 若有 tool_calls 就执行 → 结果塞回消息 → 再调 LLM。无 tool_call 即返回。 +- 工具结果对模型截 16K 字符,用户预览 400 字符 +- 后台 daemon 线程每 100ms 刷 spinner:`thinking... 1.3s ctx 12,345 tok` +- 每轮 LLM 返回追加 dim 一行 `[in N out N t Xs]` +- assistant 文本走 `rich.markdown.Markdown` 整段渲染(非流式) +- `max_iterations` 从 capabilities 读 ### 3.2 Model Profile(`core/capabilities.py` + `config/models/*.yaml`) -**核心思想**:每个模型一份 yaml 档案,agent 行为按档案动态调整。新模型 5 分钟接入,不改代码。 - -`ModelCapabilities` 字段:max/reliable_context、max_output、parallel_tools、tool_calling_quality、thinking_mode、reasoning_effort_levels、code_quality、enable_run_python、max_iterations、optimal_temperature、prompt_caching、extended_thinking、api_base、api_key_env。 - +每模型一份 yaml,agent 行为按档案动态调整。新模型 5 分钟接入,不改代码。 +字段:max/reliable_context、max_output、parallel_tools、tool_calling_quality、thinking_mode、reasoning_effort_levels、code_quality、enable_run_python、max_iterations、optimal_temperature、prompt_caching、extended_thinking、api_base、api_key_env。 `LLM.chat` 按 capabilities 自动启 `parallel_tool_calls` / `reasoning_effort` / Anthropic prompt-caching header。 ### 3.3 Capability Probing(`core/probe.py` + `cli.py probe`) -yaml 是手填的,可能错。`probe` 用真实 LLM 调用对账: -- `basic_chat`:连通性 -- `parallel_tools`:给两个独立工具,看 single response 是否 ≥2 个 tool_calls -- `thinking_mode`:对 declared=True 的模型试 reasoning_effort,看 API 是否接受 + 是否产 reasoning_content -- `long_context`:needle-in-haystack 简化版(opt-in,默认关) - -不修改 yaml,只输出 rich Table 报告。退出码 0/2/3 区分 ok / mismatch / error。**显式触发,不进启动路径**(每次启动跑会烧 API)。 +yaml 是手填的,probe 用真实调用对账:`basic_chat` / `parallel_tools` / `thinking_mode` / `long_context`(opt-in)。不改 yaml,只出 rich Table 报告。**显式触发,不进启动路径**(避免烧 API)。 ### 3.4 工具系统(Hybrid 范式) +**JSON tool call**(`tools/`):read / write / edit / glob / grep / shell / run_python / load_skill —— 离散操作。 +**Code execution**(`run_python`):tmp `.py` + subprocess + 工作目录限制 + 敏感 env 过滤(`*API_KEY *TOKEN *SECRET *PASSWORD *PRIVATE_KEY`)—— 批处理 / 算数据 / 生成文档。 +关键设计:`edit` **唯一匹配**(CoreCoder 风格,old_str 重复即报错);工具按**原子操作**切分,不做 `make_pptx()` 这种高级封装。 -**两类工具并存**: -- **JSON tool call**(`tools/`):read / write / edit / glob / grep / shell / run_python / load_skill —— 处理离散操作 -- **Code execution**(`run_python`):tmp `.py` + subprocess + 工作目录限制 + 敏感 env 过滤(`*API_KEY *TOKEN *SECRET *PASSWORD *PRIVATE_KEY`)—— 处理批处理 / 算数据 / 生成文档 - -**关键设计**: -- `edit` 用 **唯一匹配**约束(CoreCoder 风格):old_str 必须只出现一次,否则报错。防 LLM 改错地方。 -- 工具按**原子操作**切分,不做高级封装。`make_pptx()` ❌,`run_python(code)` 调 `python-pptx` ✅。粒度太粗会接收不到模型升级红利。 - -### 3.5 Skill 系统(Anthropic 渐进披露标准) -对齐 Anthropic 2025-12 开放标准,跨平台兼容(Claude Code / Codex CLI / Gemini CLI 都用)。 - -**三层加载**: -| 层 | 时机 | 内容 | Token | -|---|------|------|------| -| Discovery | agent 启动 | 仅 `name + description`,所有 skill 都读 | 几百 | -| Activation | `load_skill(name)` | 完整 SKILL.md | 1000-5000 | -| Execution | SKILL.md 指 `references/xxx` | 单个 reference 文件 | 视情况 | - -**Skill 设计原则**:写 WHY+WHAT,不写 Step 1/2/3。让模型自己规划。description 要明确具体——决定模型能否触发。 +### 3.5 Skill 系统(Anthropic 渐进披露) +对齐 Anthropic 2025-12 开放标准。三层加载:Discovery(`name + description`,几百 token)→ Activation(`load_skill(name)` 加载完整 SKILL.md,1-5K)→ Execution(SKILL.md 指 `references/xxx` 按需拉)。 +原则:写 WHY+WHAT,不写 Step 1/2/3。description 决定模型能否触发。 ### 3.6 Session 与 Task **Session**(`core/session.py`)= 消息列表 + meta,**直接 ORM 写 PG `messages` 表**(append-only,`jsonb` 存 LiteLLM 原样 payload)。 +**Task**(`core/task.py`)= Session 上层,含 mode / description / status / model / reasoning_effort / task_dir / 时间戳 / tokens。**直接 ORM 写 PG `tasks` 表**。task_dir FS 目录只存 skill 产物,无 `state.json` / `messages.json`。本地 + SaaS **同一份 schema 和 ORM**,差别只在 `ZCBOT_DB_URL`。 -**Task**(`core/task.py`)= Session 的上层概念,含 mode / description / status (active/completed/abandoned) / model / reasoning_effort / task_dir / created_at / updated_at / tokens_prompt / tokens_completion。**直接 ORM 写 PG `tasks` 表**。 +**懒创建** —— `build_agent` 不立刻 INSERT,Task / Session 在第一条 user 消息触发 `append` 时 INSERT;task_dir 目录在 skill 第一次落产物时 `mkdir(parents=True)`。启动 REPL 后立刻 `/exit` 不留 DB 行 + 不留目录。 -存储:Session / Task → PG;task_dir FS 目录只存 skill 产物(spec_lock / sections / *.docx / *.pptx 等),不再有 `state.json` / `messages.json`。每轮 `agent.run` 后 `sync_task_tokens` UPDATE 累计 tokens。**本地 + SaaS 同一份 schema 和 ORM 实现,无 adapter 抽象层**,差别只在 `ZCBOT_DB_URL`(本地连 docker compose 起的 PG / 远端 dev PG,SaaS 连生产 PG)。 +**REPL 内 task 切换** —— `/new` / `/resume [last|]`(无参列最近 10 个)/ `/done /abandon` / `/desc`。切走前 `_cleanup_if_empty` 守门:DB 无 messages **且** FS task_dir 无产物 → DELETE + rmdir;任一痕迹存在则保留。 -**懒创建** —— `build_agent` 新建分支不立刻 INSERT,Task / Session 在第一条 user 消息触发 `Session.append` 时才 INSERT;task_dir FS 目录在 skill 第一次落产物时 `mkdir(parents=True)`。启动 REPL 后立刻 `/exit` 不留 DB 行 + 不留 FS 目录,跨进程安全。 +**原子性** —— PG INSERT 天然原子;skill 产物走 `core.session.atomic_write_text`(tmp + fsync + replace)。 -**REPL 内 task 切换** —— `/new` 开新 task,`/resume [last|]` 切到已有 task(无参数列最近 10 个表格让用户选),`/done /abandon` 改状态,`/desc` 改描述。切走前 `_cleanup_if_empty` 守门:DB 里该 task 没 messages 行 **且** FS task_dir 没产物 → DELETE tasks 行 + rmdir task_dir;任一痕迹存在则保留。 - -**原子性** —— PG INSERT 天然原子,messages / tasks 写入无 0 字节风险。skill 产物(spec_lock.md / sections/*.md 等)仍走 `core.session.atomic_write_text`(tmp + fsync + replace),避免大文件写一半留半文件。 - -CLI:`chat --mode coding --desc "..." [--resume last|] [--remote ]`;`tasks [--status active|completed|abandoned]` 列任务。 +CLI:`chat --mode coding --desc "..." [--resume last|] [--remote ]`;`tasks [--status ...]`。 ### 3.7 双层记忆(`core/memory.py`) -跨 task 共享的事实(用户偏好 / 项目约定 / 模型 quirk 备忘)放 `workspace/memory/`,两层切法: +跨 task 共享的事实(用户偏好 / 项目约定 / 模型 quirk)放 `workspace/memory/`: -| 层 | 文件 | 加载时机 | 适合内容 | -|---|------|---------|---------| -| Core | `workspace/memory/core.md` | 每次 build_agent 拼进 system prompt | 跨任务高频用的精炼事实(几百 token 内) | -| Extended | `workspace/memory/extended/*.md` | 索引(标题+绝对路径)进 prompt,内容靠 `read` 工具按需拉 | 大量低频专题(API 速查 / 历史事件) | +| 层 | 文件 | 加载 | 适合 | +|---|---|---|---| +| Core | `core.md` | 每次 build_agent 进 system prompt | 跨任务高频精炼事实(几百 token) | +| Extended | `extended/*.md` | 索引(标题+绝对路径)进 prompt,内容靠 `read` 工具按需拉 | 大量低频专题 | -**system prompt 每次 build_agent 重建**,resume 也走 `_build_system_prompt` 并覆盖 `messages[0]` —— memory 演化即时生效。代价:resume 时上下文里的 system 段可能和上一轮不一样,但跨轮强一致性不是个人 agent 的痛点,memory 时效性更重要。 +**system prompt 每次 build_agent 重建**,resume 也走 `_build_system_prompt` 并覆盖 `messages[0]` —— memory 演化即时生效。 -memory 文件由人填(也允许 agent 用 `write` 写)。系统不自动维护 —— 这是和"auto memory"框架的关键差异:**事实由用户判断,不由 LLM 自动总结**(后者噪音和误判风险高)。 +memory 由人填(也允许 agent 用 `write` 写),系统不自动维护 —— 关键差异:**事实由用户判断,不由 LLM 自动总结**。 -**形态兼容** —— memory **永远在 FS,不入 DB**: -- 本地形态:`workspace/memory/{core.md, extended/}` -- SaaS 形态:`/users//memory/{core.md, extended/}`(bind mount 进容器) - -理由:① memory 本质是"用户笔记",FS 读写 + 编辑器手编是产品语义的一部分,DB 化反而要造一层 UI 让用户改 md;② 跨 task 共享靠"同一 user 看同一份目录"语义自动达成,不需要 schema 设计;③ 不参与 §7.4 表结构,task 删/folder 删都不连带 memory。memory 不分 folder,是 per-user 单一命名空间。 +**memory 永远在 FS,不入 DB**:本地 `workspace/memory/`,SaaS `/users//memory/`(bind mount 进容器)。理由:用户笔记语义,FS 读写 + 编辑器手编是产品的一部分;跨 task 共享靠"同一 user 同一目录"自动达成,无需 schema。 --- ## 4. 模型路由 -### 默认配置(`config/agent.yaml`) -```yaml -default_model: deepseek_v4.flash -``` +默认 `default_model: deepseek_v4.flash`。后续分模式路由思路: -设计上的分模式路由(后续要做)思路: | 模式 | 模型 | 理由 | -|-----|-----|------| -| 通用 / 编码 / PPT / 提案初稿 | flash | flash SWE-Bench 80.6,够用 | -| 复杂 bug / 提案终稿 | pro + reasoning_effort=max | 关键产出值得花 | +|---|---|---| +| 通用 / 编码 / PPT / 提案初稿 | flash | SWE-Bench 80.6,够用 | +| 复杂 bug / 提案终稿 | pro + reasoning_effort=max | 关键产出 | | fallback | claude_4_7.opus | V4 不行时手动切 | -### 成本量级 -| 任务 | flash | pro-max | Claude Opus 4.7 | -|-----|------|--------|------| -| 修一个 bug(~10 轮) | $0.01 | $0.05 | $0.30 | -| 5 页汇报 PPT | $0.05 | $0.20 | $1.50 | +成本量级(对比): + +| 任务 | flash | pro-max | Opus 4.7 | +|---|---|---|---| +| 修 bug(~10 轮) | $0.01 | $0.05 | $0.30 | +| 5 页 PPT | $0.05 | $0.20 | $1.50 | | 完整申报书 | $0.30 | $1.50 | $10-15 | -99% 任务 flash 够用,关键终稿升级 Pro。 +99% 任务 flash 够用,关键终稿升 Pro。 --- ## 5. 设计哲学 ### 核心原则:Less Scaffolding, More Trust -老 agent 框架(早期 LangChain、AutoGPT)失败的核心:给 LLM 太多脚手架,模型升级后这些脚手架成枷锁。 -**正确做法**:把 LLM 当一个**会持续变强的同事**对待,告诉它目标,不告诉它步骤。 +老 agent 框架失败的核心:给 LLM 太多脚手架,模型升级后这些脚手架成枷锁。**正确做法**:把 LLM 当一个**会持续变强的同事**,告诉它目标,不告诉它步骤。 ### 七条具体原则 -1. **Prompt 用 WHY+WHAT,不用 HOW** —— 详细教"应该怎么思考"会降智强模型 -2. **Skill 渐进披露,不写完整流程** —— 对齐 Anthropic 标准 -3. **工具按原子操作切分,不做高级封装** —— 留出组合空间给模型 -4. **Model Profile 化,不硬编码** —— 新模型 5 分钟接入 -5. **Capability Probing** —— yaml 是手填的,跑探测对账实际行为 -6. **版本化 Prompt** —— `prompts/system/active.md` 软链接(尚未做,等真要切版本时再做) -7. **eval 评估** —— 设计阶段曾认为是关键,落地后判断:个人工具 dogfooding 更有效;**已删** +1. Prompt 用 WHY+WHAT 不用 HOW —— 教"怎么思考"会降智强模型 +2. Skill 渐进披露,不写完整流程 +3. 工具按原子操作切分,不做高级封装 —— 留组合空间 +4. Model Profile 化,不硬编码 +5. Capability Probing 对账实际行为 +6. 版本化 Prompt(等真要切版本时再做) +7. ~~eval 评估~~ —— 已删,dogfooding 更有效 -### 借鉴自(简版) +### 借鉴 | 来源 | 借鉴 | -|-----|------| -| CoreCoder | 主循环简洁实现 + Edit 唯一匹配约束 | -| Anthropic Agent Skills | SKILL.md + 渐进披露标准 | +|---|---| +| CoreCoder | 主循环简洁实现 + Edit 唯一匹配 | +| Anthropic Skills | SKILL.md 渐进披露 | | nanobot | Workspace + 任务隔离 | -| smolagents | LiteLLM 做模型层 + CodeAct 范式启发 run_python | +| smolagents | LiteLLM + CodeAct 启发 run_python | --- ## 6. 风险与取舍 -### 已知风险 | 风险 | 缓解 | -|-----|------| -| run_python subprocess 沙盒不够强(本地形态非真隔离) | 限制工作目录 + 敏感 env 过滤;SaaS 形态走 docker exec(§7.6 #6),本地依赖用户对模型生成代码的最终审阅 | -| V4 在某些复杂任务不如 Claude | dogfooding 判断,fallback 手动切 | -| Skill description 不够好 → 触发不准 | 用 Pro 优化 description,实战观察 | -| Long context 退化 | `probe --long-context` 探测可靠 ceiling,不依赖宣称值 | -| 本地 PG 连接不稳定 / 离线 dogfood | `docker compose up -d` 一行起本地 PG 兜底;也可连远端 dev / staging PG;CI 用 ephemeral PG container | +|---|---| +| run_python sandbox 不够强(本地非真隔离) | 工作目录限制 + 敏感 env 过滤;SaaS 走 docker exec(§7.5);本地依赖用户最终审阅 | +| V4 某些复杂任务不如 Claude | dogfooding 判断,fallback 手动切 | +| Skill description 不准 → 触发不到 | Pro 优化描述,实战观察 | +| Long context 退化 | `probe --long-context` 探测可靠 ceiling | +| 本地 PG 离线 | `docker compose up -d` 起本地 PG 兜底;也可连远端 dev / staging PG | -### 取舍说明 -**为什么用 Hybrid 范式而不是纯 CodeAgent**:V4 JSON tool call 已稳定;沙盒成本只在需要时付;兼容 thinking 模式。 - -**为什么用 Anthropic Skill 标准而不是自创**:行业标准已成,跨 SDK 兼容;直接拿 Anthropic 现成 skills repo。 - -**为什么不做 subagent**:状态管理复杂度爆炸;单 agent + skill 已覆盖 95% 场景。 - -**为什么不做 Eval Suite**:DESIGN 旧版按团队/产品场景设计;个人单用户场景里,跑两个真实任务的 dogfooding 比造作 case 信号更强,probe 已覆盖健康检查。 +**Hybrid 范式而非纯 CodeAgent**:V4 JSON tool call 已稳定;sandbox 成本只在需要时付;兼容 thinking。 +**Anthropic Skill 标准**:行业标准已成,跨 SDK 兼容。 +**不做 subagent**:状态管理爆炸;单 agent + skill 已覆盖 95% 场景。 +**不做 Eval Suite**:个人单用户场景,dogfooding 信号比造作 case 强,probe 覆盖健康检查。 --- ## 7. SaaS 化(草案,status=design,2026-05-12) -> §1-§6 是 **本地 dogfood 形态**;本节是 **SaaS 形态**,把 core 包成多用户在线服务。 -> 不引入 platform/core 切分 —— core 就是后端,直接对用户做 auth(原"平台签 JWT、core 验签"多租户方案废弃)。两条形态共享同一份 core,差别只在 CLI 入口 vs HTTP 入口。本节落地前 §1-§6 路线照走,不阻塞 dogfood。 +> §1-§6 是**本地 dogfood 形态**;本节是**SaaS 形态**,把 core 包成多用户在线服务。 +> 不引入 platform/core 切分 —— core 就是后端,直接对用户做 auth。两条形态共享同一份 core,差别只在 CLI 入口 vs HTTP 入口。本节落地前 §1-§6 路线照走,不阻塞 dogfood。 ### 7.0 与本地形态的兼容性 -SaaS 化不是"重写"也不是"取代 CLI",而是**给同一份 core 加一个 HTTP 入口**。落地过程中本地 CLI 必须始终可用。 +SaaS 化不是"重写"也不是"取代 CLI",而是**给同一份 core 加一个 HTTP 入口**。落地过程中本地 CLI 始终可用。 -**两条形态共享**: -- 同一份 `core/`(loop / capabilities / skills / memory / storage 接口) -- 同一份 `tools/`(底层 executor 从 subprocess 换 docker exec,接口不变) -- 同一份 SKILL.md 和 prompts +**共享**:同一份 `core/` / `tools/` / SKILL.md / prompts。 +**差别**: -**两条形态差别**: - -| 维度 | 本地形态 | SaaS 形态 | +| 维度 | 本地 | SaaS | |---|---|---| -| 入口 | `cli.py chat ...` 直调 core | HTTP `/v1/...` + SSE | -| Storage | **PG**(`ZCBOT_DB_URL` 指 docker compose / 远端 dev PG) | **PG**(`ZCBOT_DB_URL` 指生产 PG) | -| task_dir 根 | `workspace/tasks//`(派生,task 私有) | `/users///`(用户给,可共享) | +| 入口 | `cli.py chat` 直调 core | HTTP `/v1/...` + SSE | +| Storage | **PG**(`ZCBOT_DB_URL` 指 docker compose / 远端 dev PG) | **PG**(指生产 PG) | +| task_dir 根 | `workspace/tasks//`(派生,私有) | `/users///`(用户给,可共享) | | Memory | `workspace/memory/`(FS) | `/users//memory/`(仍是 FS) | -| Sandbox | subprocess + env 过滤(非真隔离) | per-task docker exec | -| Auth | 无(单用户 `user_id='local'`) | OIDC + JWT(user_id) | +| Sandbox | subprocess + env 过滤 | per-task docker exec | +| Auth | 无(`user_id='local'`) | OIDC + JWT | -**CLI 长期双模式**: -- **本地直跑**:`cli.py chat`(默认),直接调 core in-process,直连 PG。适合 dogfood / 调 core 内部状态 -- **API client**:`cli.py chat --remote https://...`,走 HTTP /v1,跟前端用户路径一致 +**CLI 长期双模式**:本地直跑(默认,in-process,直连 PG,适合调内部状态)/ `--remote https://...`(HTTP 走 `/v1`,等价真实用户路径)。两模式共用 `cli.py`,差别只在 transport 层。 -两模式共用 `cli.py` 入口,差别只在 transport 层(in-process call vs HTTP)。dogfood ≡ 真实用户路径只在 `--remote` 模式下成立;**本地直跑模式永久保留**(调试 core 内部状态比 HTTP roundtrip 顺手)。 - -**本地 PG 连接** —— `ZCBOT_DB_URL` 指向 docker compose 起的本地 PG(`docker compose up -d` 一行起,repo 自带 `docker-compose.yml`)或远端 dev / staging PG。**离线场景靠本地 docker compose 兜底**,不靠"零依赖"幻觉。 - -`workspace/` 目录:仅存 skill 产物(spec_lock / sections / *.docx / *.pptx),state / messages 全在 PG。本地 vs SaaS 差别只在 task_dir 根路径,不在 storage 形态。 +`workspace/` 仅存 skill 产物,state / messages 全在 PG。本地 vs SaaS 差别只在 task_dir 根路径,不在 storage 形态。 ### 7.1 心智模型:Folder-centric,task-as-DB-record -参考 Claude Code(cwd 是 anchor,状态存别处)+ OpenAI Assistants(stateful agent service)。 +参考 Claude Code(cwd 是 anchor)+ OpenAI Assistants(stateful agent service)。 -- **Folder** = 用户的"硬盘",路径 `users///...`。能浏览、新建、改名、上传、下载,**和本地文件管理器体感一致**。folder 没 ID,**path 就是标识**;改名走 prefix cascade。 -- **Task** = DB 一行,带 `task_dir` 指向 folder(相对 user root)。同 folder 允许多 task,但 task 之间**不允许嵌套**(no-subtask)。 +- **Folder** = 用户的"硬盘",路径 `users///...`,**和本地文件管理器体感一致**。folder 无 ID,**path 即标识**;改名走 prefix cascade。 +- **Task** = DB 一行,带 `task_dir` 指向 folder(相对 user root)。同 folder 允许多 task,但 task 之间**不嵌套**(no-subtask)。 - **Messages** = DB 表,append-only,`jsonb` 存 LiteLLM 原样 payload。 -- **Skill 运行产物** 全落 cwd,不引入 artifacts 表;终稿后 SKILL.md 指示 agent 清中间件。 -- **Skill 定义** 是项目代码,跟部署走,所有用户共享,不入用户 folder。 +- **Skill 产物**全落 cwd,不引入 artifacts 表;SKILL.md 指示 agent 清中间件。 +- **Skill 定义**是项目代码,跟部署走,所有用户共享。 -**task_dir 在两形态的对应**(§7.0 总览的展开): -- 本地形态:`task_dir = workspace/tasks//`(派生,task 私有,无并发写冲突) -- SaaS 形态:`task_dir = /users///`(用户给,可被同 user 多 task 共享) +state / messages 两形态都在 PG,FS 只承担 skill 产物。多 task 共享同 folder 时由 §7.8 文件级悲观锁兜底。 -state / messages **两形态都在 PG**,FS 只承担 skill 产物(sections / *.docx / 中间件)。多 task 共享同 folder 时由 §7.8 文件级悲观锁兜底(并发写同名文件冲突早失败,推到模型自纠)。 - -### 7.2 资源模型与接口(/v1) +### 7.2 资源模型(/v1) ``` -POST /v1/folders 创建 -GET /v1/folders 列树 -GET /v1/folders/{path} 详情(task 列表 + 文件列表) -PATCH /v1/folders/{path} 改名/移动(prefix cascade) -DELETE /v1/folders/{path} hard cascade(连带 task+messages,前端二确认) - -POST /v1/folders/{path}/files 上传(multipart) -GET /v1/folders/{path}/files[/{name}] 列 / 下载 -DELETE /v1/folders/{path}/files/{name} - -POST /v1/tasks 创建({task_dir, mode, desc, model}) -GET /v1/tasks 列(?task_dir= ?status= 过滤) -GET /v1/tasks/{id} 详情 -PATCH /v1/tasks/{id} 改 mode/desc/status -DELETE /v1/tasks/{id} 删 task(messages 一起删,不动 cwd 文件) - -POST /v1/tasks/{id}/messages 发消息,返回 {run_id} -GET /v1/tasks/{id}/messages 历史(?search= 走 jsonb GIN / tsvector) -GET /v1/tasks/{id}/runs/{run_id}/events SSE 事件流 -POST /v1/tasks/{id}/runs/{run_id}/cancel - -GET /v1/skills | /v1/models | /v1/usage -POST /v1/probe (admin) 跑 capability probe +POST/GET/PATCH/DELETE /v1/folders[/{path}] 列树 / 创建 / 改名 / hard cascade +GET/POST/DELETE /v1/folders/{path}/files[/{name}] 列 / 上传 / 下载 / 删 +CRUD /v1/tasks[/{id}] {task_dir, mode, desc, model} +POST/GET /v1/tasks/{id}/messages 发消息 / 历史(?search= 走 jsonb GIN / tsvector) +GET/POST /v1/tasks/{id}/runs/{run_id}/{events,cancel} SSE +GET /v1/{skills,models,usage} +POST /v1/probe (admin) ``` **SSE 事件**:`tool_call` / `tool_result` / `text` (delta) / `usage` / `done`,带 `run_id`。 +**版本化**:`/v1` minor 半年向后兼容,major 6 个月 deprecation。 -**版本化**:`/v1` minor 半年内向后兼容,major 6 个月 deprecation。 +### 7.3 认证 -### 7.3 认证模型 - -OIDC / Clerk / 自建邮箱登录,JWT 只带 `user_id` claim: - -``` -Authorization: Bearer -X-Request-Id: -``` - -所有 storage/executor 调用 scoped by `user_id`。**无 tenant 层** —— 个人 SaaS 用不上,日后做企业版加 `org_id` claim 等价隔离。 +OIDC / Clerk / 自建邮箱登录,JWT 只带 `user_id` claim:`Authorization: Bearer ` + `X-Request-Id`。所有 storage/executor scoped by `user_id`。**无 tenant 层** —— 个人 SaaS 用不上,做企业版再加 `org_id` 等价隔离。 ### 7.4 存储:Postgres + 本地文件系统 ```sql users(user_id uuid pk, email null, password_hash | oidc_subject null, plan null, created_at) --- 本地形态固定 INSERT 一行 sentinel: user_id = '00000000-0000-0000-0000-000000000000', --- email / auth / plan 全 NULL;CLI 启动时若不存在则建,tasks 全部 FK 到它 +-- 本地形态固定 INSERT sentinel: user_id = '00000000-...',email/auth/plan 全 NULL -tasks( - task_id uuid pk, - user_id uuid fk, - task_dir text not null, -- 相对 user root,如 "project_a/sub" - mode text, -- coding / proposal / ppt / chat - description text, - status text, -- pending / running / paused / done - model_profile text, - tokens_prompt int default 0, - tokens_completion int default 0, - cost_usd numeric default 0, - created_at timestamptz, - updated_at timestamptz -); +tasks(task_id uuid pk, user_id fk, task_dir text not null, mode, description, + status, model_profile, tokens_prompt, tokens_completion, cost_usd, + created_at, updated_at); create index on tasks (user_id, task_dir); -messages( - message_id uuid pk, - task_id uuid fk, - idx int not null, - payload jsonb not null, -- LiteLLM dict 原样 - tokens_in int, tokens_out int, - created_at timestamptz, - unique (task_id, idx) -); +messages(message_id uuid pk, task_id fk, idx int not null, + payload jsonb not null, tokens_in, tokens_out, created_at, + unique (task_id, idx)); create index on messages using gin (payload jsonb_path_ops); --- 对话全文搜按需加 tsvector + GIN(中文起步 simple + pg_trgm) +-- 全文搜按需加 tsvector + GIN(中文 simple + pg_trgm 起步) -runs(run_id uuid pk, task_id fk, status, started_at, finished_at, error, tokens_p, tokens_c) +runs(run_id pk, task_id fk, status, started_at, finished_at, error, tokens_p, tokens_c) usage_events(id, user_id, task_id uuid, run_id uuid, kind, value, ts) --- append-only。task_id/run_id 不 FK,task 硬删后审计记录仍存活 +-- append-only。task_id/run_id 不 FK,task 硬删后审计仍存活 ``` -**No-subtask 校验**(`create_task` 入口): +**No-subtask 校验**(`create_task`):查同 user 下是否存在 `new LIKE existing/%` 或 `existing LIKE new/%`,中一则拒;同 task_dir 允许。 -```sql -SELECT 1 FROM tasks - WHERE user_id = ? - AND ( ? LIKE task_dir || '/%' -- new 在已有之下 → 拒 - OR task_dir LIKE ? || '/%' ); -- 已有在 new 之下 → 拒 --- 同 task_dir 允许(同 folder 多 task) -``` +**Folder rename**(`old → new`,FS rename 成功后):`UPDATE tasks SET task_dir = new || substring(task_dir from len(old)+1) WHERE user_id=? AND (task_dir = old OR task_dir LIKE old||'/%')`。**用 `old/%` 而非 `old%`**,避免 `project_a` 误中 `project_a_other`。running task 引用时禁 rename / delete。 -**Folder rename**(改名 `old → new`,FS rename 成功后跑): - -```sql -UPDATE tasks - SET task_dir = ? || substring(task_dir from char_length(?) + 1) -- new, old - WHERE user_id = ? AND (task_dir = ? OR task_dir LIKE ? || '/%'); -- old, old -``` - -LIKE 用 `old/%` 而非 `old%`,避免 `project_a` 误中 `project_a_other`。**running task 引用该 folder 时禁 rename / delete**(后端校验 + UI 禁按钮)。 - -**Folder delete**:hard cascade,前端 modal 列影响面("将删 N 个对话、M 条消息、K 个文件")+ 输入 folder 名二确认。 - -```sql --- 先 DB 后 FS;DB 失败 FS 不动一致;DB 成功 FS 失败由后台 GC 兜底清孤儿目录 -DELETE FROM messages - WHERE task_id IN (SELECT task_id FROM tasks - WHERE user_id=? AND (task_dir=? OR task_dir LIKE ?||'/%')); -DELETE FROM tasks - WHERE user_id=? AND (task_dir=? OR task_dir LIKE ?||'/%'); --- 然后 FS 递归删 folder -``` - -`usage_events` 不参与 cascade(审计 append-only)。 +**Folder delete**:hard cascade,前端 modal 列影响面 + 输入 folder 名二确认。先 DELETE messages → DELETE tasks → FS 递归删;DB 成功 FS 失败由后台 GC 兜底清孤儿目录。`usage_events` 不参与 cascade。 **文件系统**: - ``` /users// - memory/{core.md, extended/} # 跨 task 的 per-user 记忆,不入 DB - project_a/source/ sections/ proposal.docx - project_b/... + memory/{core.md, extended/} # per-user,不入 DB + /... # task_dir 散落其下 ``` +本地优先 S3(部署简化 / 低延迟),storage 抽象层留好后续可换。 -本地优先 S3(简化部署 / 低延迟),storage 抽象层留好后续可换 backend。 - -**Storage 实现:单一 PG ORM**(本地 + SaaS 共用): -- 一份 schema、一份 ORM(SQLAlchemy)、一份查询代码,无 adapter 抽象层,无 SQL 方言适配,无契约测试 -- 本地 dev 连接:`ZCBOT_DB_URL=postgresql://...` 环境变量;repo 自带 `docker-compose.yml` 起本地 PG(零配置)或连远端 dev / staging PG -- Schema 演化:alembic 管理 migration,`db/migrations/*.py` 与代码一同版本化;CLI 启动校验当前 schema 版本,落后报错让用户跑 `cli db upgrade`(本地)或部署管线自动 `alembic upgrade head`(SaaS) -- 旧 workspace JSON 一次性迁移:`cli migrate-from-fs --workspace ./workspace` 把 `state.json` / `messages.json` 导入 PG,完成后 workspace 进只读 archive 模式 -- 本地单用户 sentinel:DB init 时若 users 表无 sentinel 行则 INSERT;本地 CLI 所有 tasks 全 FK 到这一行,无 auth 流程,但 schema 与 SaaS 完全一致 -- memory 不参与:per-user FS,两形态都不入 DB +**Storage 实现:单一 PG ORM**(本地 + SaaS 共用):一份 schema、一份 SQLAlchemy、一份查询,无 adapter,无 SQL 方言适配,无契约测试。alembic 管 migration;CLI 启动校验 schema 版本,落后报错让用户跑 `cli db upgrade`(本地)或部署管线自动 `alembic upgrade head`(SaaS)。`cli migrate-from-fs --workspace ./workspace` 一次性导旧 JSON。 ### 7.5 沙盒:Per-task 容器 + Per-run exec | 选择 | 理由 | |---|---| | 每 task 长驻容器 | 起容器 ~300ms 太慢;多轮 tool call 共享划算 | -| 每 run 一次 `docker exec` | exec 级 timeout/资源限制 | +| 每 run 一次 `docker exec` | exec 级 timeout / 资源限制 | | 空闲 N 分钟回收 | 不浪费,resume 时拉起 | -| **bind mount = user root** | `/users//` → 容器 `/workspace`;同用户多 task 不互隔(协作方便),跨用户由独立容器实例隔离 | +| bind mount = user root | `/users//` → `/workspace`;同 user 多 task 不互隔(协作方便),跨 user 由独立实例隔离 | **资源限制**:cgroup CPU/mem、磁盘配额、egress allowlist(只放 LLM + PyPI 镜像)、root fs read-only、no-new-privileges、drop ALL caps。 -**选型**:起步 Docker(运维门槛低);流量起来后视情况换 gVisor / Firecracker / e2b。Executor Protocol 抽象后切换成本低。 +**选型**:起步 Docker;流量起来后视情况换 gVisor / Firecracker / e2b。Executor Protocol 抽象后切换成本低。 ### 7.6 Core 代码改造(按依赖顺序) -| # | 项 | 影响文件 | 估时 | -|---|---|---|---| -| 1 | ~~事件流化 `loop.py`~~ | 已完成(commit `375bb29`) | — | -| 2 | **Storage 落 PG**:`Session` / `TaskState` 改 SQLAlchemy ORM 写 PG `messages` / `tasks` 表(单一实现,无 adapter 抽象);alembic 管 schema migration;`cli migrate-from-fs` 一次性把现有 workspace JSON 导入;repo 加 `docker-compose.yml` 起本地 PG 用于 dev | `core/session.py` `core/task.py` 新增 `core/storage/` `db/migrations/` `cli.py::migrate_from_fs` `cli.py::db_upgrade` `docker-compose.yml` `requirements.txt` | 3 天 | -| 3 | **task_dir 双形态共存**:`TaskState.task_dir` 可显式指定(本地默认 `workspace/tasks//`,SaaS = 用户给路径);`tools/fs.py::_resolve` 接受 task_dir 注入;system prompt 注入逻辑两形态共用 | `core/task.py` `tools/fs.py` `main.py` `prompts/system/general_v1.md` | 1 天 | -| 4 | **Folder API**:list / create / rename(cascade + 锁 running task) / delete(hard cascade,前端二确认强校验) / upload / download | 新增 `core/folders/` | 2 天 | -| 5 | **No-subtask 校验**:`create_task` 入口跑 §7.4 的 SQL | `core/task.py` | 0.5 天 | -| 6 | **Executor + 沙箱**:`run_python`/`shell` → `Executor.run(...)`,`docker exec` 到 per-user/per-task 容器;`api_key_env` → `KeyProvider`(运行时注入);**本地形态保留 subprocess executor**,SaaS 形态走 docker executor | `tools/run_python.py` `tools/shell.py` `core/capabilities.py` `core/llm.py` 新增 `core/executor/` | 2-3 天 | -| 7 | **HTTP /v1**:FastAPI + SSE + OIDC | 新增 `core/api/` `core/auth/` | 4 天 | -| 8 | **CLI 双模式**:加 transport 层抽象 —— 无 `--remote` 时走 in-process 直调 core(本地形态);`--remote ` 走 HTTP API client(dogfood ≡ 真实用户路径);**不删除本地直跑** | `cli.py` 加 `core/transport/` | 1.5 天 | +| # | 项 | 估时 | +|---|---|---| +| 1 | ~~事件流化 `loop.py`~~(commit `375bb29`) | done | +| 2 | **Storage 落 PG**:`Session` / `TaskState` 改 SQLAlchemy 写 PG;alembic;`cli migrate-from-fs`;`docker-compose.yml` 起本地 PG | 3 天 | +| 3 | **task_dir 双形态**:`TaskState.task_dir` 可显式指定;`tools/fs.py::_resolve` 接 task_dir 注入;system prompt 注入两形态共用 | 1 天 | +| 4 | **Folder API**:list / create / rename(cascade + 锁 running) / delete(hard cascade) / upload / download | 2 天 | +| 5 | **No-subtask 校验**:`create_task` 入口跑 §7.4 SQL | 0.5 天 | +| 6 | **Executor + sandbox**:`run_python`/`shell` → `Executor.run(...)`;本地保留 subprocess executor,SaaS 走 docker;`api_key_env` → `KeyProvider` 运行时注入 | 2-3 天 | +| 7 | **HTTP /v1**:FastAPI + SSE + OIDC | 4 天 | +| 8 | **CLI 双模式**:transport 层抽象,默认 in-process;`--remote` 走 HTTP;**本地直跑不删** | 1.5 天 | -代码量增量:**+1000~1500 行**(单一 PG 实现比双 adapter 方案省 500-800 行;无契约测试集 / 无方言适配层)。 +代码量增量:**+1000~1500 行**(单一 PG 比双 adapter 省 500-800 行)。 ### 7.7 分阶段落地 | 阶段 | 范围 | 工作量 | 验收 | |---|---|---|---| -| A | §7.6 #1 | done | ✅ | -| B | §7.6 #2 #3 #4 #5(Storage 落 PG + task_dir 双形态 + Folder API + no-subtask) | ~1 周 | 本地 CLI 走 PG,messages 进 DB 可全文搜;多 task + folder rename 单测过;`migrate-from-fs` 跑通 | -| C | §7.6 #6(Executor + sandbox) | 3 天 | 两本地账号互不可见对方 folder,本地 subprocess executor 仍可用 | -| D | §7.6 #7(HTTP /v1 + auth) | 4 天 | curl/Postman 跑通主流程 | -| E | §7.6 #8(CLI transport 双模式) | 1.5 天 | CLI 默认本地直跑保留,`--remote` 走 HTTP 也跑通 | +| A | #1 事件流化 | done | ✅ | +| B | #2 #3 #4 #5(Storage 落 PG + task_dir 双形态 + Folder API + no-subtask) | ~1 周 | 本地走 PG,messages 进 DB 全文搜可用;多 task + folder rename 单测;`migrate-from-fs` 跑通 | +| C | #6(Executor + sandbox) | 3 天 | 两本地账号互不可见对方 folder,本地 subprocess executor 仍可用 | +| D | #7(HTTP /v1 + auth) | 4 天 | curl / Postman 跑通主流程 | +| E | #8(CLI transport 双模式) | 1.5 天 | 默认本地直跑保留,`--remote` 走 HTTP 跑通 | | F | 上线打磨(限流 / 监控 / 告警 / HA) | 持续 | SLO 99.5% | -**B 阶段一次性切换** —— 切到 PG 后本地与 SaaS 走相同代码路径,无回退、无双轨。**dogfood 即生效**(messages 进 DB → 全文搜、jsonb 查询立刻可用)。前置:repo 提供 `docker-compose.yml`,作者本机 `docker compose up -d postgres` 一行准备好 dev DB。 +**B 阶段一次性切换** —— 切到 PG 后本地与 SaaS 走相同代码路径,无回退、无双轨。**dogfood 即生效**(messages 进 DB → 全文搜、jsonb 查询立刻可用)。 ### 7.8 已知风险 | 风险 | 缓解 | |---|---| -| 过早抽象违背 §5 哲学 | B 阶段单一 PG 实现无 adapter 抽象层;C-E 各阶段独立 dogfood 价值,"先有场景再加" | -| 本地 PG 连接 / 离线 dogfood | `docker compose up -d` 本地起 PG 兜底;也支持连远端 dev / staging PG;CI 用 ephemeral PG container | -| CLI 双模式分叉、本地直跑被忽略 | transport 层抽象统一接口;CI 跑 in-process 和 HTTP 两路径同一组用例 | +| 过早抽象违背 §5 | B 阶段单一 PG 无 adapter;C-E 各阶段独立 dogfood 价值 | +| CLI 双模式分叉、本地直跑被忽略 | transport 层抽象统一接口;CI 跑两路径同一组用例 | | `/v1` 冻死后演化慢 | minor 半年兼容,major 6 个月 deprecation;`/v1internal` 实验 | -| Rename 误命中前缀 / 漏改子 task | cascade SQL + 单测覆盖 `project_a` 不中 `project_a_other` | -| 运行中 task 被 rename / delete | 后端校验 + UI 禁按钮 | -| 误删 folder 丢对话 | 前端二确认 + 输入 folder 名;真要再加 trash bin(延迟 cascade) | -| DB-then-FS 中断留孤儿目录 | 后台 GC 周期扫 "FS 有但 DB 无引用" 的目录 | -| 同 folder 多 task 并发写同名文件 | 文件级悲观锁,冲突早失败 | -| Sandbox 出站越权 | egress allowlist 起步只放 LLM + PyPI 镜像 | -| 资源滥用(LLM / 存储) | BYO key 默认;月度 token & 存储配额;cold task LRU 清 | +| Rename 误中前缀 / 漏改子 task | cascade SQL 用 `old/%` + 单测覆盖 | +| Running task 被 rename / delete | 后端校验 + UI 禁按钮 | +| 误删 folder | 二确认 + 输入 folder 名;真要再加 trash bin | +| DB-then-FS 中断留孤儿目录 | 后台 GC 周期扫"FS 有但 DB 无引用" | +| 同 folder 多 task 并发写同名 | 文件级悲观锁,冲突早失败 | +| Sandbox 出站越权 | egress allowlist 起步只放 LLM + PyPI | +| 资源滥用 | BYO key 默认;月度配额;cold task LRU 清 | ### 7.9 取舍说明 -**path-as-identity 而非 folder_id**:folder 真实存在于 FS,folder_id 等于造两份 source of truth(易不一致)。rename 是 UI 主动动作,cascade 单事务搞定。 +**path-as-identity 而非 folder_id**:folder 真实存在于 FS,folder_id 等于造两份 source of truth。rename 是 UI 主动动作,cascade 单事务搞定。 -**user auth 而非 tenant 层**:个人 SaaS 用不上。日后做企业版加 `org_id` claim,数据隔离规则等价。提前抽象 MVP 多 NULL 一层。 +**user auth 而非 tenant 层**:个人 SaaS 用不上。企业版加 `org_id` claim 等价。 -**skill 中间件全落 cwd 不引入 artifacts 表**:中间件是用户花 token 生成的资产,可下载可替换;artifacts 表 + 分类是为不确定的 UX 收益预付架构成本。真嫌乱 UI 加折叠视图。 +**skill 产物全落 cwd 不引入 artifacts 表**:中间件是用户花 token 生成的资产,可下载可替换;artifacts 表是为不确定 UX 收益预付架构成本。真嫌乱 UI 加折叠视图。 -**hard cascade 而非 soft orphan**:`orphaned` 让 list/resume/UI 都多一种特殊 case,代码长尾;"删 folder = 删项目" 比 "留对话残骸" 自然。`usage_events` append-only 不 FK,task 硬删后月账仍存活。 +**hard cascade 而非 soft orphan**:`orphaned` 让 list / resume / UI 都多一种特殊 case,"删 folder = 删项目"比"留对话残骸"自然。`usage_events` append-only 不 FK,task 硬删后月账仍存活。 -**Docker + Postgres 起步**:运维门槛最低,Executor 抽象层留好,切 microVM / S3 都是 backend 替换不动接口。 +**本地也用 PG,不用 SQLite / JSON**:① dogfood ≡ 真实用户路径,bug 在 dogfood 就能复现;② Docker 已是必然依赖(§7.5),`docker compose up postgres` 零增量门槛;③ 双 adapter 维护税远高于 PG 一次性配置成本;④ 本地 dev 也能连远端测试服。 -**本地也用 PG,不用 SQLite / JSON**: -1. **dogfood ≡ 真实用户路径** —— 本地与 SaaS 走相同 SQL 方言、相同事务语义、相同 ORM,bug 在 dogfood 阶段就能复现,不会等到生产 -2. **Docker 已经是必然依赖** —— §7.6 #6 沙盒走 docker exec;装 Docker 是前提,顺手 `docker compose up postgres` 是零增量门槛 -3. **双 adapter 维护税远高于 PG 一次性配置成本** —— 一份 schema、一份 ORM、一份查询;SaaS 起步即终态,切换成本归零 -4. **本地 dev 也能连测试服** —— 不强迫本机起 PG,作者可直接连远端 dev / staging PG 跑 dogfood,体感跟连 SaaS 几乎一致 +**CLI 不被 API 取代,而是双模式共存**:本地直跑调 core 内部状态比 HTTP roundtrip 顺手;前端用户路径靠 `--remote` 打通。离线靠本地 docker compose PG 兜底,不靠"全栈零依赖"幻觉。 -**CLI 不被 API 取代,而是双模式共存**:本地直跑模式调 core 内部状态比 HTTP roundtrip 顺手;前端用户路径靠 `--remote` 模式打通。transport 层抽象代价小、长期价值高 —— 删本地直跑省不下多少代码,反而失去最便利的调试入口。**离线**靠本地 docker compose PG 兜底,不靠"全栈零依赖"幻觉。 +**Memory 不入 DB**:跨 task 共享靠"同一 user 同一 FS 目录"自动达成。md 用户直接编辑器改,DB 化反而要造 UI、违反 §3.7"事实由用户判断"。 -**Memory 不入 DB**:跨 task 共享靠"同一 user 看同一份 FS 目录"的语义自动达成,不需要 schema。md 文件用户直接编辑器改,DB 化反而要造 UI、违反 §3.7 "事实由用户判断" 原则。两形态 memory 行为一致(只是根目录不同),迁移零成本。 - -**为什么 Tasks/Messages 在 PG 但 skill 产物在 FS**:tasks / messages 是元数据 + 对话流,需要查询、过滤、全文搜、跨 task 统计 —— 都是 DB 强项,jsonb GIN / pg_trgm 让查询代码不爆炸。skill 产物(`*.pptx` / `*.docx` / `sections/*.md`)是终用户拿走的文件,期望直接在文件管理器看到、用 Office 打开、邮件附件发出去 —— 进 DB 就要做"导出"这一步多余操作,且二进制 BLOB 在 PG 里没 GIN 索引价值。**FS 是产物的天然存储,DB 是元数据 / 状态 / 查询索引的天然存储,各司其职**。同理 §7.5 沙盒 bind mount = user root,容器里看到的就是用户在 Web UI 里看到的目录,无中间层翻译。 +**Tasks/Messages 在 PG 但 skill 产物在 FS**:tasks / messages 需要查询、过滤、全文搜、跨 task 统计 —— DB 强项;skill 产物(`*.pptx` / `*.docx` / `sections/*.md`)终用户拿走,期望文件管理器看到、Office 打开、邮件发出 —— 进 DB 要做"导出"多余操作。**FS 是产物天然存储,DB 是元数据 / 状态 / 索引天然存储**。同理 §7.5 bind mount = user root,容器里 ≡ 用户在 Web UI 看到的目录,无中间层翻译。 --- ## 附录:DeepSeek V4 关键事实(2026-04-24) -- **V4-Pro**:1.6T 总 / 49B 激活,1M context,SWE-Bench 80.6 / Terminal-Bench 67.9 / MCPAtlas 73.6 -- **V4-Flash**:284B 总 / 13B 激活,1M context -- 三种推理模式:non-thinking / thinking / thinking-max -- 价格:输入 ~$0.145/M,输出 ~$1.74/M(约 Claude Opus 1/6 ~ 1/7) -- `deepseek-chat` / `deepseek-reasoner` 在 2026-07-24 后下线 → 必须迁 `deepseek-v4-flash` / `deepseek-v4-pro` +- **V4-Pro**:1.6T / 49B 激活,1M context,SWE-Bench 80.6 / Terminal-Bench 67.9 / MCPAtlas 73.6 +- **V4-Flash**:284B / 13B 激活,1M context +- 推理模式:non-thinking / thinking / thinking-max +- 价格:in ~$0.145/M,out ~$1.74/M(约 Claude Opus 1/6 ~ 1/7) +- `deepseek-chat` / `deepseek-reasoner` 2026-07-24 下线 → 必须迁 `deepseek-v4-flash` / `deepseek-v4-pro` diff --git a/PROGRESS.md b/PROGRESS.md index ec6812e..127508f 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -1,55 +1,49 @@ # 实施进度 -> 配合 `DESIGN.md` 阅读。本文件只记录 phase 状态、决策偏差、文件量、下一步。 +> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。 -最后更新:2026-05-12(§7 改写为 user-direct SaaS 草案) +最后更新:2026-05-12 --- ## 状态 | Phase | 标题 | 状态 | 备注 | -|------|-----|-----|------| +|---|---|---|---| | 1-3 | 骨架 + Skill + run_python | ✅ | 三个 skill;CoreCoder 唯一匹配 edit;敏感 env 过滤 | | 4 | 演化性能力 | 🟡 | Model Profile + Probing ✅;版本化 prompt 未做 | | 5 | Eval Suite | ⏸ 不做 | dogfooding 替代,probe 覆盖健康检查 | -| 6 | 长任务工程化 | 🟡 | task + state.json + 恢复 ✅;双层记忆 ✅;context 压缩未做 | +| 6 | 长任务工程化 | 🟡 | task + 恢复 ✅;双层记忆 ✅;context 压缩未做 | | 7 | 打磨 | ❌ | Docker 沙盒 / 更多 skill | -| §7 SaaS 化 | DESIGN §7 路线 | 🟡 | A 事件流化 ✅;B (Storage 落 PG + task_dir 双形态 + Folder API + no-subtask) 可立刻开,本地与 SaaS 共用同一种 storage | +| §7 SaaS | DESIGN §7 路线 | 🟡 | A 事件流化 ✅;B(Storage 落 PG + Folder API)可立刻开 | --- ## 已完成关键能力 -**2026-Q1 ~ 05-06:Phase 1-4** —— 骨架 / 三个 skill(coding/ppt/proposal)/ run_python 范式 / Model Profile + Capability Probing。`ppt` v3:商务红约束 + apply_brand + Iconify + render_icon/quality_check;素材摄取改 markitdown CLI。 - -**2026-05-06:Phase 6 部分** —— task + state.json + tokens 累计;CLI `tasks` + REPL `/status /done /abandon /desc`;移除 legacy `workspace/sessions/`。 - -**2026-05-07:TUI 打磨 + task_dir 落地** —— rich Markdown 渲染;thinking spinner 显实时耗时+累计 token;system prompt 注入 task_dir 绝对路径,skill 产物全收敛 `workspace/tasks//`;`.gitignore` 删 bandaid 行。 - -**2026-05-08:REPL task 切换 + 懒创建** —— `/resume [last|]`;`build_agent` 不预占文件,首条 user 消息触发 save;`_cleanup_if_empty` 三条件守门防误删。 - -**2026-05-09 → 05-10:§7 草案 + 对话导出** —— DESIGN §7 初版 SaaS 草案(后于 05-12 重写);`cli.py export ` + `core/export_docx.py` 导对话成 docx。 - -**2026-05-11:原子写 + 双层记忆 + §7 A** —— `atomic_write_text` 接管 save;`core/memory.py` 双层记忆(core.md 入 prompt,extended/* 走索引);loop 事件流化(`sink.emit`)铺 SSE 路。 - -**2026-05-12:§7 改写** —— 原 platform/core 多租户方案废弃,改 user-direct(folder-centric,task/messages 入 PG,no-subtask 约束,hard cascade delete)。 +- **Q1 → 05-06 / Phase 1-4**:骨架 / 三 skill / run_python / Model Profile + Probing。ppt v3 加商务红 + apply_brand + Iconify;素材摄取改 markitdown CLI。 +- **05-06 / Phase 6 部分**:task + state.json + tokens 累计;CLI `tasks` + REPL `/status /done /abandon /desc`;移除 legacy `workspace/sessions/`。 +- **05-07 / TUI + task_dir**:rich Markdown 渲染;spinner 显实时耗时 + 累计 token;system prompt 注入 task_dir 绝对路径,产物收敛 `workspace/tasks//`;`.gitignore` 删 bandaid。 +- **05-08 / REPL 切换 + 懒创建**:`/resume [last|]`;`build_agent` 不预占文件;`_cleanup_if_empty` 三条件守门。 +- **05-09 → 05-10 / §7 草案 + 导出**:DESIGN §7 初版(05-12 重写);`cli.py export ` + `core/export_docx.py`。 +- **05-11 / 原子写 + 双层记忆 + §7 A**:`atomic_write_text` 接管 save;`core/memory.py`(core.md 入 prompt,extended/* 走索引);loop 事件流化(`sink.emit`)铺 SSE 路。 +- **05-12 / §7 改写**:platform/core 多租户方案废弃,改 user-direct(folder-centric、task/messages 入 PG、no-subtask、hard cascade)。 --- ## 关键决策与偏差 | 项 | 决策 | 备注 | -|---|------|------| +|---|---|---| | 工具基目录 | cwd(读)+ task_dir(写) | system prompt 同时注入两者绝对路径 | | Workspace 布局 | `tasks//` + `memory/{core.md, extended/}` | memory 跨 task 共享 | -| Eval Suite | 不做 | 个人工具用 dogfooding | +| Eval Suite | 不做 | 个人工具 dogfooding | | 版本化 prompt | 直接 `general_v1.md` | Windows 软链接麻烦,真要切再做 | | run_python 沙盒 | subprocess + env 过滤 | Docker 在 §7 C 阶段 | --- -## 文件清单(代码量) +## 文件清单 ``` core/capabilities.py 71 @@ -80,9 +74,7 @@ Python 合计 ~2429 行 ## 下一步候选(性价比排序) -1. **§7 B 阶段**(~1 周)—— Storage 落 PG(单一实现,无 adapter 抽象)+ task_dir 双形态 + Folder API + No-subtask。**dogfood 即生效**(messages 进 DB → 全文搜立刻可用)。 - - 前置:repo 加 `docker-compose.yml`(`docker compose up -d postgres` 起本地 dev PG)或 `ZCBOT_DB_URL` 指向远端测试 PG - - 里程碑:① schema + alembic 初版迁移 ② SQLAlchemy ORM 接入 `Session` / `TaskState` ③ CLI 适配(去 `.json` 读写,加 `_cleanup_if_empty` 新逻辑)④ `cli migrate-from-fs` 工具(把现有 `workspace/tasks/*/` 导入 PG)⑤ Folder API + no-subtask SQL 校验 ⑥ 本地单用户 sentinel(`user_id='00000000-...'`)init 流程 -2. **Phase 6 context 三层压缩**(~1 天)—— 兜底,V4 长上下文一般用不到 -3. **Phase 7 更多 skill / 模型档案**(持续) -4. **Proposal mermaid 流程图预渲染**(~半天)—— ASCII 透传不够用时再上 `mmdc`(Node.js 依赖) +1. **§7 B 阶段**(~1 周)—— Storage 落 PG(单一实现)+ task_dir 双形态 + Folder API + No-subtask。**dogfood 即生效**(messages 进 DB → 全文搜可用)。里程碑:schema + alembic → ORM 接入 Session/TaskState → CLI 适配 → `migrate-from-fs` → Folder API + no-subtask SQL → 本地 sentinel user init。 +2. **Phase 6 context 三层压缩**(~1 天)—— 兜底,V4 长上下文一般用不到。 +3. **Phase 7 更多 skill / 模型档案**(持续)。 +4. **Proposal mermaid 预渲染**(~半天)—— ASCII 透传不够用时再上 `mmdc`。