05-实施约束与依赖.md 22.7 KB

05-实施约束与依赖

5.1 现有资产复用清单(来自本地 Hermes profile 调研,架构文档第 9 节)

Org Agent 复用来源 复用内容 迁移动作
Planner ~/.hermes/profiles/planning/ SOUL.md(16 条根本原则);planning-requirement-expansion、project-brd-planning、project-prd-planning、project-sdd-planning(本 SDD 采用的方法论本身)、project-unit-decomposition、review-blocker-verification 等 skill SOUL.md 正文迁移到目标项目 .claude/agents/planner.md(frontmatter 配 skills:/memory: project/tools:/model:,见架构文档 7.2 节),skill 迁移到 graphlemon/skills/* 供 skills: 字段引用
Coder ~/.hermes/profiles/code/ SOUL.md(认领→实现→测单→证据回写);code-task-development、code-agent-engineering、debugging-toolkit、software-planning-execution 迁移到 .claude/agents/coder.md + graphlemon/skills/*,需要改造:原流程强绑定飞书 Base 认领机制,改为消费 task 表 + c_org_node_trigger
Validator ~/.hermes/profiles/qa/ SOUL.md + AGENTS.md(三态判定、双向可追溯矩阵、工作区权限契约);qa-task-validation、unattended-agent-operations 迁移到 .claude/agents/validator.md(AGENTS.md 的工作区权限契约折入 tools/disallowedTools frontmatter),双向可追溯矩阵直接对应 04-P p_validator_root_cause_trace 的血缘追溯步骤

不复用:三个 profile 的 memories/MEMORY.md/USER.md(内容重叠、未按角色隔离);.archive/、migration/、openclaw-imports/ 下的通用助理技能(apple、creative、feishu-lark 等)。

5.2 飞书 Base 替换计划(架构文档 12.1 节已确认)

  • 不做历史数据自动迁移,MVP 从空任务队列开始运行
  • 原 lark-cli --as user 的认领/回写语义,改由 c_org_node_trigger + task.status 状态机承接
  • 原飞书 Base 的人工可视化能力,由本 SDD 02-V 的 v_task_board/v_task_detail/v_graph_visualizer 承接

5.3 执行后端范围(架构文档 7.2 节决策,2026-07-29,取代此前"双后端不对等"框架)

  • MVP 阶段只实现并使用 Claude Code ACP 一种执行后端,不考虑 Hermes CLI 作为执行后端。此前"Org Graph 层两后端一致、Work Graph 层两后端不对等"的处理方案已作废——不存在第二个后端。
  • Org Graph 层:c_org_node_trigger 底层是 claude --agent <planner|coder|validator> -p "...",Org Agent 身份由目标项目 .claude/agents/*.md 承载(见 5.1 节迁移动作)
  • Work Graph 层:c_work_graph_execute 跑完整的 fan-out/fan-in/loop 能力(见 03-C 3.5 节),MVP 验收标准是这一条能跑通即可,不再需要"至少验证一种后端"这类留有余地的表述
  • v_graph_visualizer 不再需要"简化模式"展示逻辑(02-V 2.7 节保持单一展示模式)
  • Executor Interface 的抽象层设计仍保留为未来扩展点,但不是当前实现范围

5.4 依赖的外部能力(对应 00 节 A-1,已通过 spike 实测确认)

  • Claude Code 动态工作流的 agent()/parallel()/pipeline() 原语已实测确认可通过 claude -p(非交互式)编程方式调用,方案 A 成立(spikes/a1-dynamic-workflows/,真实账号验证,2026-07-29,成本 $0.46):
    • claude -p 'Run /<workflow> with args {...}' --output-format json --json-schema <schema> < /dev/null 无交互提示、无挂起、40s 内完成,structured_output 精确匹配预期输出,permission_denials 为空
    • 发现的约束:不能加 --bare。--bare 会跳过登录态/凭据发现(报 Not logged in,与是否存在 workflow 无关),必须用不带 --bare 的完整冷启动调用,代价是启动稍慢,但可靠性优先
    • 具体实现契约(写入 workflow 脚本、子进程调用、解析 structured_output、回填预算用量)已落地到 03-C 3.5 节
  • 原风险已解除,不再需要在实施第一周单独排期做技术验证;架构文档 10.1 节的方案 B(完全自研 Graph Engine)作为长期备选保留,暂不启用

5.4.1.1 --agent 与 --json-schema 不能同时生效(发现,2026-07-31,U-011)

  • 实测确认:claude --agent <name> -p ... --json-schema <schema> 时,--json-schema 被静默忽略——返回的 JSON payload 里 structured_output 字段整个不存在,模型改成把结构化内容以自由文本 + ```json 围栏代码块的形式塞进 result 字段里;但 --agent 本身工作正常(人设确实生效,回复里引用了迁移后 SOUL.md 的具体条目编号)
  • 这条约束和 5.4 节 A-1 spike 的场景不同:A-1 验证的是不带 --agent 的动态工作流调用(claude -p 'Run /<workflow>...'),--json-schema 在那种调用形式下是生效的(structured_output 精确匹配);只有同时传 --agent 和 --json-schema 时才会出现这个静默失效
  • 应对方式:需要"某个 Org Agent 身份 + 结构化输出"同时成立时(如 c_work_graph_plan,见 03-C 3.4 节/U-011),不要依赖 --json-schema/structured_output,改为在 prompt 里明确要求模型把结果包在单个 ```json 围栏代码块里,再从 result 字段用正则提取解析,对"整段就是 JSON"和"围栏代码块"两种情况都做兜底解析(参考实现:src/work_graph_plan.py 的 _extract_graph_manifest)
  • 未在 Anthropic 官方文档里找到这条限制的说明,是靠实测发现的,不排除后续版本行为会变——如果这里升级过 Claude Code 版本,值得重新验证一次

5.4.1.2 subagent frontmatter 的 disallowedTools 不支持按子命令限定(发现,2026-07-31,U-014 真实闭环跑出来的)

  • 实测确认:.claude/agents/<name>.md frontmatter 里的 disallowedTools,只要出现任意 Bash(某子命令:*) 这种按子命令限定的写法(如 Bash(git commit:*)),Bash 工具会被整体禁用,不是只挡住那一条子命令——即使同一份 frontmatter 的 tools: 字段里明确写了 Bash。用一个只有 tools:[Read,Bash]、disallowedTools:[Edit,Write](不含任何 Bash(...))的最小测试 agent 验证过 Bash 能正常跑;一旦给 disallowedTools 加一条 Bash(git commit:*),同一个 agent 立刻连 echo 都跑不了
  • claude --help 里 --disallowedTools/--allowedTools CLI 参数本身明确支持这种按子命令限定的写法(帮助文本示例就是 Bash(git *) Edit),但 subagent frontmatter 里的同名字段不支持同一种语法——是两个不同的解析路径,行为不一致,官方文档没有说明这个差异
  • 实际后果:U-004 迁移 Validator 人设时,原本想用 disallowedTools: [Edit, Write, Bash(git commit:*), Bash(git push:*), Bash(git reset:*), Bash(git clean:*)] 把"不准改代码"和"不准动 git 历史"都在工具层锁死,结果是 Validator 真实调用时完全没有 Bash 权限,无法运行任何测试——这个问题一直存在到 U-019 之后才通过跑 U-014 完整真实闭环时暴露出来(Validator 给出 INCONCLUSIVE 判定时明确说"没有 Bash 工具")。已订正为 disallowedTools: [Edit, Write](不再限定 Bash 子命令),"不准动 git 历史"这条约束现在只靠 AGENTS.md 里的文字指令,不是工具层强制——一个跑不了任何测试的 Validator 比一个理论上可能跑 git commit 的 Validator 风险大得多,两权相害取其轻
  • 同 §5.4.1.1:这条限制没在官方文档里找到说明,是实测发现的,不排除后续版本行为会变——升级 Claude Code 版本后值得重新验证

5.4.1.3 ThreadingHTTPServer + 共享 sqlite 连接:关闭连接必须持有同一把锁(发现,2026-07-31,U-027)

  • 这不是 Claude Code 的行为,是 backend_server.py 自己的并发 bug,记录在这里是因为它和上面两条一样"没写测试根本发现不了、实测才炸出来",属于同一类"实现约束"。
  • 背景:U-022 起 backend_server.py 给 ThreadingHTTPServer(每个连接一个线程)配了一个跨线程共享的 sqlite 连接(schema.connect(..., check_same_thread=False)),配了 server.conn_lock 要求所有读写都在锁内进行。U-024/U-027 又加了长连接 WebSocket 处理器,在后台线程里用同一把锁循环轮询。
  • 实测确认:_Server.server_close()(测试 tearDown/进程正常关闭时都会走到)原来是 self.conn.close() 直接关,没拿 conn_lock。如果这一刻恰好有个 WS 轮询线程正在锁内执行 SQL,两个线程在没有任何同步的情况下并发操作同一个 C 级 sqlite3 连接对象——不是干净地抛 ProgrammingError,是直接把 Python 解释器崩了(exit code 139 / SIGSEGV)。命中概率取决于时序窗口,单独跑某个测试文件时几乎必现,跑整个套件时因为时序被摊薄,時有时无,不修的话会是一个典型的"本地测试大多数时候是绿的,CI 偶尔莫名其妙崩溃"式 flaky 源。
  • 应对方式:self.conn.close() 也必须包在 with self.conn_lock: 里——"关闭"和"读写"对同一个连接对象而言都是需要互斥的操作,不能默认关闭是安全的。已在 U-027 修复,修复后连续跑几十次全部稳定通过。
  • 这条不需要等 Claude Code 升级重新验证(跟它无关),但如果以后这里改用连接池/每线程一个连接,这条约束本身会自然消失,到时候可以直接删掉这一节。

5.4.1.4 c_skill_diff/c_skill_rollback 的仓库路径不能通过 HTTP 暴露(设计约束,2026-07-31,U-029)

  • 背景:Skill 库是 graphlemon 自己平台的资产(不是 target project 的),c_skill_rollback(src/skill_update.py 的 rollback_skill())会在这个仓库里真的跑 git commit;repo_path 参数 U-018 就有意不给默认值,防止哪个调用方忘了传就误伤 graphlemon 自己的历史(见 U-018 那次实现补充)。
  • 风险点:backend_server.py 把这个函数接到 POST /api/skills/:skill_id/rollback 时,如果 HTTP 层内部写死 skill_repo_path=<graphlemon 真实仓库根>,那么任何针对这条路由的自动化测试(src/test_backend_server.py)都会在正在跑的这个 graphlemon 仓库里留下真实 git 提交——测试本身就会污染开发者自己的仓库历史。
  • 应对方式:参照 create_server() 已有的 db_path 参数模式,加了 skill_repo_path 参数:默认值是 skill_query._REPO_ROOT(graphlemon 真实仓库根,生产环境下这就是唯一正确的行为——Skill 变更本来就应该落到这里),但可以在构造 server 时覆盖;测试统一传一个 tempfile 建的临时仓库。这个参数只在 Python 层(进程启动/测试构造)可设,绝不通过 HTTP 请求体/query string 暴露给客户端——否则等于开了一个"客户端指定任意目录、服务端替它跑 git commit"的口子,属于路径注入/任意写入范畴。
  • 这不是"发现"的约束(不是实测跑出来的意外行为),是设计阶段就该考虑到、写代码时主动做的防护,记录下来是为了以后改这块代码的人不要图省事把 skill_repo_path 从请求里读出来。

5.4.1.5 动态工作流内部 agent() 的 agentType+schema 组合已实测可用(发现,2026-08-01,U-031)

  • 背景:U-031 补 Work Graph 三模块集成(work_graph_orchestrate.py)时,每个 Work Node 的真实执行都要用一份一次性的动态工作流脚本,脚本内唯一的一次 agent() 调用需要同时做到两件事——① 加载目标 org_node(Planner/Coder/Validator)迁移后的人设(agentType: '<org_node>',对应 .claude/agents/<org_node>.md),② 拿到结构化输出(schema: {...})。这和 §5.4.1.1 记录的"顶层 --agent + --json-schema 不能同时生效"表面上是同一类组合,但机制完全不同:§5.4.1.1 是 CLI 顶层参数 claude --agent ... --json-schema ...;这里是动态工作流脚本内部 JS DSL 的 agent(prompt, {agentType, schema}) 调用,二者互不影响,不能假设前者的限制也适用于后者——必须单独验证,不能凭直觉套用。
  • 实测确认(两次独立验证):① 先用一个最小 spike(仅一个一节点工作流,唯一动作是 agent({agentType:'planner', schema:{type:'object',...}}))验证——structured_output 正确按 schema 返回,且额外追问角色身份时模型明确自称"Planning Agent (PL)",证明迁移后的 SOUL.md 人设确实通过 agentType 正确加载(不是退化成某个通用 agent 后蒙对了格式);② 后续 e2e_pipeline.plan_documents() 完整四节点(BRD→PRD→SDD→Benchmark)真实跑通,再次在真实多节点场景下确认这个组合稳定可用。
  • 实际用法:work_graph_orchestrate._render_node_script() 渲染的脚本固定形如 const result = await agent(prompt, { agentType: <org_node>, schema: <output_schema> }),execute_work_graph() 直接把这个一次性脚本文件写到 .claude/workflows/ 后调用 claude -p 'Run /<name>...' 执行,不复用同一个脚本名跑第二个节点(每个 node_exec_id 生成自己独立的脚本文件名,避免并发/重试之间互相覆盖)。
  • 同 §5.4.1.1/§5.4.1.2:这条结论是实测发现的,官方文档没有专门说明动态工作流 agent() 调用里 agentType+schema 的组合行为,不排除后续 Claude Code 版本变化后需要重新验证。

5.4.2 Claude Code plan 额度等待重试参数(架构文档 7.5 节决策,2026-07-29)

  • 退避轮询间隔(5→10→20→30 分钟,上限 30 分钟)、超时兜底阈值(session 8 小时、weekly 9 天)是 MVP 阶段的硬编码默认值,不在管理后台开放用户配置,和 log_retention_config 的"保留天数可配、清理频率不可配"是同一个 MVP 简化原则
  • 这组参数的合理性依赖 Claude Code 官方文档给出的窗口周期(5 小时滚动 / 7 天)本身不变;若未来 Claude Code 调整了额度窗口周期,需要同步调整这里的超时阈值,不是一次性写死可以忽略的常量

5.3.1 c_work_graph_execute 权限模式决策(03-C 3.5 节,2026-07-29)

  • 选定 --permission-mode auto,而非 --dangerously-skip-permissions/bypassPermissions:后者官方文档明确要求只能在隔离容器/VM+无网络访问环境下使用;graphlemon 目前的节点隔离手段(git worktree)是同机目录级隔离,达不到这个前提,用 bypassPermissions 会暴露 prompt injection 风险
  • 约束 1(模型选型):auto mode 要求会话模型为 Claude Sonnet 5 / Opus 4.7+ / Fable 5(Anthropic API 上),架构文档 10 节"按节点分层选模型"给 Coder/Validator 节点选便宜模型时不能选到这个集合以外
  • 约束 2(失败模式):-p 非交互模式下,分类器连续拦截 3 次或累计 20 次会导致会话直接中止(无人可确认),对应 c_work_graph_execute 新增 CLASSIFIER_BLOCKED 错误码,路由到 blocked_pending_human(04-P 已补充)
  • 待办(非 MVP 阻塞项,值得后续排期):auto mode 支持通过"受信任基础设施"配置(autoMode.environment)减少误拦截,architecture.md 目前未定义 graphlemon 自己的可信基础设施清单(例如目标项目的 git remote、允许访问的包registry等),MVP 阶段先接受更高的误拦截率、靠人工在 v_task_detail 复核放行,后续可评估是否要把这份配置纳入 org_graph_definition 或新增专门的设置页

5.4.1 控制台载体形态的实施约束(架构文档 13.1 节决策 5,已确认)

  • 控制台是本地 Web 应用,后端是本机常驻进程(同时也承担 Graph Engine 的调度职责),前端是普通网页,两者本地通信(如 localhost 端口)
  • c_pick_folder 依赖后端所在操作系统的原生对话框能力:macOS 用 osascript,Windows/Linux 需要各自的原生方案(如 PowerShell 脚本 / zenity/kdialog),实施时需要按目标操作系统分别验证,不是一套代码天然跨平台
  • 若后端运行在无图形界面的环境(纯服务器/容器),c_pick_folder 会返回 DIALOG_UNAVAILABLE,v_project_setup 需要有手动路径输入的兜底交互(MVP 阶段可以直接做成兜底默认项,不必等图形环境不可用时才现场适配)

5.5 上线顺序与兼容性

  1. 先落地 01-M 的数据模型(task/artifact/skill 等核心表)+ Git 仓库目录结构(架构文档 7.3 节)
  2. 再落地 Org Graph 层接口(3.1-3.3, 3.11-3.13)+ p_org_task_lifecycle 状态机,此时可先用手工调用验证 Planner/Coder/Validator 三个 Org Agent 分别可被触发
  3. 最后落地 Work Graph 层(3.4-3.6)与 Claude Code 动态工作流的集成;A-1 已通过 spike 实测确认(见 5.4 节),可按 03-C 3.5 节的已验证实现契约直接开发,无需再单独排期做技术验证

5.6 迁移与回滚约束

  • org_graph_definition 的变更(Org Graph 结构本身)走人工 PR 评审流程,不允许系统自主提交
  • skill 的自主迭代(c_skill_update)允许系统自主提交,但必须可通过 c_skill_rollback 一键回滚到迭代前版本
  • MVP 阶段数据库 schema 变更暂不考虑蓝绿发布/双写迁移(单用户本地运行,允许停机迁移)——例外:graphlemon 平台自身的 self-target 场景不适用这条"允许停机迁移"的简化假设,见 5.8 节

5.7 graphlemon 自身实现技术栈(决策,2026-07-30)

  • graphlemon 自身的后端(HTTP API + WebSocket/SSE server + Graph Engine 调度)与 PH5 的 Deploy Supervisor 进程,统一采用纯 stdlib Python 实现,不依赖任何第三方包(不用 Flask/FastAPI/Express/Node.js),对齐前身项目 graphloop 的既有约定(graphloop/CLAUDE.md原话:"Everything is stdlib: argparse, sqlite3, http.server, subprocess, threading, unittest, etc.")
  • 前端是本地 Web 应用(浏览器页面),后端用 http.server/socketserver 等 stdlib 原生能力提供 HTTP API;WebSocket 若 stdlib 支持不足,实施时(U-007)需要验证并可退化为 SSE/长轮询,但仍在 stdlib 范围内解决,不引入第三方包;前端不引入 npm/构建步骤,可以是原生 HTML/CSS/JS 或参考 graphloop graph.html/project.html 的单文件 no-build 页面模式
  • 例外:这条约束只针对 graphlemon 平台自身的实现语言,不代表 graphlemon 生成/审查的目标项目代码也必须是 Python——目标项目用什么语言由目标项目自己的技术栈决定
  • 影响:development-plan.md 的 U-007(本地后端骨架)、U-008(前端骨架)、U-036(Deploy Supervisor 骨架)的 Done 标准隐含"纯 stdlib"这条约束
  • 源码目录约定(2026-07-30 补充,2026-07-31 订正):graphlemon 平台自身的后端源码(U-001 的 schema 实现、U-007 起的 HTTP/WebSocket 后端)落在 graphlemon 仓库根目录的 src/。这与目标项目仓库自己已有的 src/ 是两个完全不同的目录,分属两个物理仓库(见架构文档 §13.1"目标项目是独立代码空间"决策)——PH5 self-target 场景下二者会同时存在于同一台机器上,实现时不要混淆,必要时以绝对路径而非相对路径区分。订正:目标项目的 src/ 不是 U-002 生成的——U-002 只在目标项目里创建 .graphlemon/ 子目录(架构文档 §13.2),目标项目自己的 src/ 是其原有源码目录,graphlemon 不创建也不规定其结构;development-plan.md 此前 U-002 那行误引用了架构文档 §7.3 的旧目录树,已于 2026-07-31 订正(§7.3 是"graphlemon 只服务自己开发自己"阶段的旧设计,已被 §13 取代,§7.3 里的 .claude/agents/、skills/、顶层 runs/ 实际是 graphlemon 平台自己仓库的资产,属于 U-003 范围)

5.8 self-target 场景治理规则(self-development-feasibility.md 缺口 1/3,2026-07-30 回填)

  • worktree 无条件强制:target_folder_path == graphlemon 平台自身安装路径 时,无论并行还是串行,Coder/Validator 一律强制使用 git_worktree_ref,不适用 03-C 3.6 节"仅并行写代码节点才强制申请 worktree"的一般规则——这是该规则的 self-target 特例
  • core_engine 分类路由:bug_record.root_cause_category 新增枚举值 core_engine,覆盖 .claude/agents/*.md 身份文件变更、Graph Engine 调度器/执行器抽象层核心逻辑变更、01-M 自身数据库 schema 变更三类场景。命中 core_engine 时,c_org_loop_route(03-C)路由到 PH5 的 p_deploy_supervisor_blue_green 流程(04-P),而不是常规的 fast_lane/full_replan
  • 不要求提交前人工评审:core_engine 改动不强制在合并主分支前经过人工评审(此前"比照 org_graph_definition 强制人工 PR 评审"的提案已被用户明确否决),改为合并后由 Deploy Supervisor 持续监控运行时行为、检测异常时自动回滚(见 04-P p_deploy_supervisor_blue_green、docs/supervisor-auto-rollback-design.md)。5.6 节"允许停机迁移"的简化假设不适用于这类 self-target 场景——self-target 的 schema 变更同样走蓝绿部署+监控流程,不是简单停机迁移

5.9 Deploy Supervisor MVP 固定参数(docs/supervisor-auto-rollback-design.md §9,2026-07-30 回填)

参数 草案值 说明
HEALTH_CHECK_STARTUP_TIMEOUT 30s Candidate 进程从拉起到能响应探活
HEALTH_CHECK_HTTP_TIMEOUT 10s 单次 /api/health 请求
HEALTH_CHECK_CANARY_TIMEOUT 120s 一次最小 canary 任务从提交到出结果
MONITOR_SAMPLE_INTERVAL 5min process/http 采样间隔
CANARY_INTERVAL 1h 监控期 canary 任务运行间隔
MAX_CRASH_COUNT / CRASH_WINDOW 2 次 / 10min 判定进程反复崩溃
MAX_CONSECUTIVE_HTTP_FAIL 3 次 判定健康端点持续不可用
VALIDATOR_RATE_DEVIATION_THRESHOLD ±30 个百分点 判定 Validator 行为异常(validator_approval_rate 信号阈值)
MONITORING_WINDOW_HOURS 24h 监控窗口时长上限
MONITORING_MIN_TASK_COUNT 10 个真实任务 监控窗口任务数上限,先到者结束

与 5.4.2 节额度等待参数的差异:5.4.2 节的退避/超时参数依据是 Anthropic 平台文档定义的外部事实(5 小时/7 天窗口周期),可以合理标注为"MVP-fixed";本节这组阈值是纯粹的工程经验草案,没有类似的外部权威来源,标注为"可调,需在真实跑通几轮自我修改后实测校准",不建议实现时直接当成不可变常量硬编码进测试断言。