From ad02cb8fef1648766edc70dc7dbd5fd10a1a19ce Mon Sep 17 00:00:00 2001 From: cfdaily Date: Wed, 10 Jun 2026 12:31:55 +0800 Subject: [PATCH] =?UTF-8?q?docs:=2020-task-type-architecture.md=20v2.0=20-?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20=C2=A711-=C2=A713=20PromptSection=20?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design/20-task-type-architecture.md | 251 +++++++++++++++++++++-- 1 file changed, 233 insertions(+), 18 deletions(-) diff --git a/docs/design/20-task-type-architecture.md b/docs/design/20-task-type-architecture.md index 38efd00..0304ad9 100644 --- a/docs/design/20-task-type-architecture.md +++ b/docs/design/20-task-type-architecture.md @@ -1,7 +1,7 @@ --- title: "TaskTypeRegistry + Handler 架构重构" created: 2026-06-10 -version: v1.0 +version: v2.0 --- # §1 现状分析 @@ -121,6 +121,14 @@ class TaskTypeHandler(Protocol): def check_completion(self, task_id: str, db_path: Path) -> bool: """检查任务是否已完成(如 mail 的回复检查)。""" ... + + def get_sections(self) -> list['PromptSection']: + """返回此 handler 的 prompt section 列表。 + + 返回有序的 PromptSection 列表,由 PromptComposer 统一拼装。 + build_prompt 可使用此列表自动拼装,也可 override 做自定义(向后兼容)。 + """ + ... ``` **设计原则**: @@ -319,41 +327,42 @@ for vp in TaskTypeRegistry.virtual_projects(): 按风险从低到高排列,每步完成后跑 `pytest -m "not e2e"` 全量回归测试。 -### Step 1:注册表基础设施 +### Step 1:注册表 + PromptComposer 基础设施 -- 新建 `src/daemon/task_type_registry.py` -- 定义 `TaskTypeHandler` Protocol -- 定义 `TaskTypeRegistry` 类 -- 编写单元测试验证注册/查询机制 +- 新建 `src/daemon/task_type_registry.py`:`TaskTypeHandler` Protocol + `TaskTypeRegistry` +- 新建 `src/daemon/prompt_composer.py`:`PromptSection` Protocol + `PromptContext` + `PromptComposer` +- 编写单元测试验证:注册/查询、section 排序/去重/条件过滤 - **风险**:极低,纯新增文件,不改动现有代码 ### Step 2:TaskHandler - 新建 `src/daemon/task_handler.py` -- 从 spawner/dispatcher 的 default 分支封装 handler 方法 +- 实现 5 个 section(TaskContext / PriorOutputs / RoleSkill / TaskApi / TaskConstraints) +- `build_prompt` 内部用 PromptComposer 拼装 - 注册到 TaskTypeRegistry - 运行全量回归测试,验证普通任务路径不变 -- **风险**:低,只是把现有逻辑包一层,不改行为 +- **风险**:低,现有 BootstrapBuilder 逻辑包一层 ### Step 3:ToolchainHandler - 新建 `src/daemon/toolchain_handler.py` -- 全新实现,无迁移成本 +- 实现 3 个 section(ToolchainContext / ToolchainApi / ToolchainConstraints) - 注册到 TaskTypeRegistry -- **风险**:低,全新代码,不影响现有路径 +- **风险**:低,全新代码 ### Step 4:MailHandler - 新建 `src/daemon/mail_handler.py` +- 实现 3 个 section(MailContext / MailApi / MailConstraints) - 从 dispatcher / spawner / ticker 三处迁移 mail 逻辑 - 注册到 TaskTypeRegistry - 重点回归测试 mail 路径:发送、回复、重试、幻觉门控 -- **风险**:中,逻辑从三处集中,需确保行为一致 +- **风险**:中 ### Step 5:引擎清理 - 删除 dispatcher / spawner / ticker 中的旧 if/else 分支 -- 统一走 handler 路径 +- 删除或保留 BootstrapBuilder(作为 TaskContextSection 的内部实现) - 全量回归测试 + 手工验证 - **风险**:中,删除代码需确保无遗漏 @@ -408,15 +417,221 @@ for vp in TaskTypeRegistry.virtual_projects(): --- +# §11 Prompt 构建现状分析 + +当前系统的 prompt 构建有四条独立路径,散落在三个文件中: + +| 路径 | 位置 | 结构 | 服务对象 | +|------|------|------|----------| +| BootstrapBuilder 4 段 | `bootstrap.py` | 任务上下文 → 前序产出 → 角色规范(Skill全文) → 硬约束 | 普通任务 | +| Mail inform 模板 | `spawner.py` MAIL_INFORM_TEMPLATE | from/to/title/text 纯文本 | mail 通知 | +| Mail request 模板 | `spawner.py` MAIL_REQUEST_TEMPLATE | from/to/title/text + API 指令 | mail 请求 | +| Toolchain 模板 | `toolchain_templates.py` + `templates/toolchain/*.md` | 5 个 md 文件占位符渲染 | 工具链事件 | + +此外 `_build_api_section` 在 spawner 中为所有路径拼 API 操作指令,但 success_status 不同(mail="done",其他="review")。 + +**问题**: + +1. **四条路径完全独立**,共性逻辑(API 指令、约束注入)重复实现 +2. **新增 task type 需要理解所有路径**才能加入 +3. **没有统一的 token 预算管理** +4. **段的顺序硬编码**在各自的拼装逻辑中 + +--- + +# §12 PromptSection 模式 + +基于知识库优秀实践(Hermes 10层有序注入、Microsoft 三层中间件、我们自己的四层加载架构),引入 PromptSection 模式。 + +## 核心思想 + +把 prompt 拆成有序的 section 列表,handler 声明自己需要哪些 section,PromptComposer 统一拼装。 + +## PromptSection 协议 + +```python +class PromptSection(Protocol): + """一个 prompt 段""" + name: str # 段名(去重用,同名覆盖) + priority: int # 排序优先级(小数字=靠前) + + def render(self, context: PromptContext) -> str: + """渲染此段的文本内容 + + Args: + context: 包含 task_id, title, description, must_haves, + project_id, agent_id, task, role 等信息的上下文对象 + Returns: + 此段的文本内容(可以为空字符串表示不注入) + """ + ... + + def should_include(self, context: PromptContext) -> bool: + """是否注入此段(默认 True,条件段可覆盖) + + 例如:前序产出段只在有 depends_on 时注入 + """ + return True +``` + +## PromptContext 数据对象 + +```python +@dataclass +class PromptContext: + """Prompt 渲染的统一上下文""" + task_id: str + title: str + description: str + must_haves: str + project_id: str + agent_id: str + task: Optional[Dict] = None + role: str = "executor" + spawn_type: str = "executor" + # mail 专用 + from_agent: str = "" + mail_type: str = "" # inform / request + # toolchain 专用 + event_type: str = "" # ci_failure / review_request / ... + event_data: Dict = field(default_factory=dict) + # 前序产出 + depends_on_outputs: Optional[List] = None +``` + +## PromptComposer 拼装器 + +```python +class PromptComposer: + """有序拼装 prompt sections""" + + def __init__(self): + self._sections: List[PromptSection] = [] + + def add(self, section: PromptSection) -> None: + """添加一个 section(同名覆盖)""" + self._sections = [s for s in self._sections if s.name != section.name] + self._sections.append(section) + + def add_many(self, sections: List[PromptSection]) -> None: + """批量添加""" + for s in sections: + self.add(s) + + def compose(self, context: PromptContext) -> str: + """拼装最终 prompt + + 1. 过滤 should_include=False 的段 + 2. 按 priority 排序 + 3. 逐段 render + 4. 用分隔符连接 + 5. Token 预算警告(不截断) + """ + active = [s for s in self._sections if s.should_include(context)] + active.sort(key=lambda s: s.priority) + parts = [s.render(context) for s in active] + parts = [p for p in parts if p.strip()] # 过滤空段 + return "\n\n---\n\n".join(parts) +``` + +## Section 优先级约定 + +| 优先级范围 | 用途 | 示例 | +|------------|------|------| +| 10-19 | 任务上下文 | 任务标题/描述、Mail 内容、Toolchain 事件 | +| 20-29 | 前序信息 | depends_on 产出、handoff comment | +| 30-39 | 角色规范 | Skill 全文注入、工具链行为指引 | +| 40-49 | API 操作指令 | 状态回写、curl 示例 | +| 50-59 | 硬约束 | 安全红线、禁止行为 | +| 60-69 | 扩展段 | 保留给未来使用 | + +--- + +# §13 三个 Handler 的 Section 注册 + +每个 handler 通过 `get_sections()` 声明自己需要的 section 列表。 + +## TaskHandler sections + +```python +def get_sections(self) -> list[PromptSection]: + return [ + TaskContextSection(priority=10), # BootstrapBuilder 段 1 + PriorOutputsSection(priority=20), # BootstrapBuilder 段 2(有 depends_on 时) + RoleSkillSection(priority=30), # BootstrapBuilder 段 3(Skill 全文) + TaskApiSection(priority=40), # API 操作指令,success_status="review" + TaskConstraintsSection(priority=50), # 硬约束 + ] +``` + +| Section | 来源 | 共性/个性 | +|---------|------|------------| +| TaskContextSection | BootstrapBuilder 段 1 | 个性:title/desc/must_haves 格式 | +| PriorOutputsSection | BootstrapBuilder 段 2 | 个性:只有 task 有 depends_on | +| RoleSkillSection | BootstrapBuilder 段 3 | 个性:只有 task 读 Skill 全文 | +| TaskApiSection | spawner `_build_api_section` | **共性基础 + 个性参数**(success_status) | +| TaskConstraintsSection | BootstrapBuilder 段 4 | 个性:每种 task 约束不同 | + +## MailHandler sections + +```python +def get_sections(self) -> list[PromptSection]: + return [ + MailContextSection(priority=10), # from/to/title/text,区分 inform/request + MailApiSection(priority=40), # API 操作指令,success_status="done" + MailConstraintsSection(priority=50), # 硬约束(禁止状态转换命令等) + ] +``` + +| Section | 来源 | 共性/个性 | +|---------|------|------------| +| MailContextSection | MAIL_INFORM_TEMPLATE / MAIL_REQUEST_TEMPLATE | 个性:邮件格式 | +| MailApiSection | spawner `_build_api_section` 变体 | **共性基础 + 个性参数**(success_status="done",含 Mail API 指令) | +| MailConstraintsSection | 模板中的 ⚠️ 约束 | 个性 | + +## ToolchainHandler sections + +```python +def get_sections(self) -> list[PromptSection]: + return [ + ToolchainContextSection(priority=10), # 事件类型 + 事件详情 + ToolchainApiSection(priority=40), # API 操作指令,success_status="done" + ToolchainConstraintsSection(priority=50), # 硬约束 + ] +``` + +| Section | 来源 | 共性/个性 | +|---------|------|------------| +| ToolchainContextSection | toolchain_templates.py + md 文件 | 个性:事件格式 | +| ToolchainApiSection | spawner `_build_api_section` 变体 | **共性基础 + 个性参数** | +| ToolchainConstraintsSection | 新增 | 个性 | + +## Section 复用分析 + +| Section | task | mail | toolchain | 是否可复用 | +|---------|------|------|-----------|-------------| +| *ContextSection | ✅ | ✅ | ✅ | ❌ 格式完全不同,各自实现 | +| *ApiSection | ✅ | ✅ | ✅ | ⚠️ 基础框架可复用(BaseApiSection),success_status 参数化 | +| *ConstraintsSection | ✅ | ✅ | ✅ | ❌ 约束内容不同,各自实现 | +| PriorOutputsSection | ✅ | ❌ | ❌ | 仅 task | +| RoleSkillSection | ✅ | ❌ | ❌ | 仅 task | + +**结论**:ApiSection 可以抽一个 BaseApiSection(curl 模板 + success_status 参数),其余 section 各自实现。 + +--- + ## 附录:文件结构预览 ``` src/daemon/ ├── task_type_registry.py # §3 + §4:Protocol + Registry -├── task_handler.py # §5 TaskHandler -├── mail_handler.py # §5 MailHandler -├── toolchain_handler.py # §5 ToolchainHandler -├── dispatcher.py # §6 改动:handler 查询替代 if/else -├── spawner.py # §6 改动:handler 查询替代 if/else -└── ticker.py # §6 改动:自动发现虚拟项目 +├── prompt_composer.py # §12 PromptSection + PromptContext + PromptComposer +├── task_handler.py # §13 TaskHandler + 5 sections +├── mail_handler.py # §13 MailHandler + 3 sections +├── toolchain_handler.py # §13 ToolchainHandler + 3 sections +├── dispatcher.py # §6 改动 +├── spawner.py # §6 改动 +├── ticker.py # §6 改动 +├── bootstrap.py # 保留,TaskContextSection 内部调用 +└── toolchain_templates.py # 保留,ToolchainContextSection 内部调用 ```