05-实施约束与依赖.md
22.9 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>.mdfrontmatter 里的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/--allowedToolsCLI 参数本身明确支持这种按子命令限定的写法(帮助文本示例就是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(模型选型):
automode 要求会话模型为 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 阻塞项,值得后续排期):
automode 支持通过"受信任基础设施"配置(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 上线顺序与兼容性
- 先落地 01-M 的数据模型(
task/artifact/skill等核心表)+ Git 仓库目录结构(架构文档 7.3 节) - 再落地 Org Graph 层接口(3.1-3.3, 3.11-3.13)+
p_org_task_lifecycle状态机,此时可先用手工调用验证 Planner/Coder/Validator 三个 Org Agent 分别可被触发 - 最后落地 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 或参考 graphloopgraph.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-Pp_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 个真实任务 | 监控窗口任务数上限,先到者结束 |
LIVE_PORT |
8420 | 生产端口固定值(2026-08-02,U-039 补充——原表遗漏;控制台前端全走同源相对路径 fetch,运行时本身不需要这个常量,但连续多次 cutover 必须落在同一端口,人工书签/固定入口才有意义) |
与 5.4.2 节额度等待参数的差异:5.4.2 节的退避/超时参数依据是 Anthropic 平台文档定义的外部事实(5 小时/7 天窗口周期),可以合理标注为"MVP-fixed";本节这组阈值是纯粹的工程经验草案,没有类似的外部权威来源,标注为"可调,需在真实跑通几轮自我修改后实测校准",不建议实现时直接当成不可变常量硬编码进测试断言。