diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index bfb2853..b1639e4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -638,6 +638,35 @@ graph = g.compile(checkpointer=PostgresSaver(...), interrupt_before=["accept"]) - **HITL 恢复 + 真相源边界**:用 `interrupt_before=["accept"]` + checkpointer 支持"写章请求返回、验收请求恢复"的跨请求流程。**正文以 `chapters` 表、审稿结果以 `chapter_reviews` 表为权威真相源;checkpoint 只存"图走到哪 + 待裁决句柄",不作正文/审稿的真相源**——恢复时按 `chapter_no` 从领域表重读,避免双真相源(对齐 ADR-2)。 - **编排 vs Agent**:图做控制流与事务边界;Agent 节点只做单次认知任务、经记忆库交换(不直连)。 +#### 5.2.1 实现现状(单章流程)与多章链图 ← 回写自实现审计 + Chain Workflow 交付(2026-06-23) + +**单章流程的实现现状(与上面"一张图串全程"的理想形态有别)**:上图是设计意图;实际**单章流程未编译成这一张图**—— +- **写章**:`POST /projects/{id}/chapters/{no}/draft` 直接 `gateway.stream()` 流式产草稿(`stream_chapter_draft`),**不经图**。(曾有 `build_write_graph` 单节点图但无端点调用,已在多章链图落地时删除;`write_node` 本体保留供链复用。) +- **四审**:`POST /projects/{id}/chapters/{no}/review` 编译 `build_review_graph()` 做**一次性 `ainvoke`**(START→四审并行→collect→END),**不带 checkpointer**。 +- **验收**:`POST /projects/{id}/chapters/{no}/accept` 是**确定性事务代码** `run_accept_transaction`(晋升终稿→从终稿提炼 digest→裁决留痕,§5.5),**不是图节点**;其 HITL 闸在 API/DB 层(冲突未裁决→`CONFLICT_UNRESOLVED`),**非 langgraph `interrupt`**。 +- 因此单章流程是线性的,**`interrupt` / Postgres checkpointer 在单章流程未实际启用**——线性流无需图。 + +**多章工作流链(Chain Workflow)= 本项目首个真正用 LangGraph cyclic 图 + Postgres checkpointer + `interrupt` 的场景。** 设计契约见 `docs/design/chain-workflow.md`。对标竞品「一键多章」,差异化在「每章过四审、遇冲突才停人」。 + +```python +# ChainState(只存控制流, 不存正文/上下文/冲突明文): {project_id, chain_key, +# start/last/current_chapter_no, written:[], has_conflicts} +# 节点各自用独立短 session(每章 accept 章原子提交); gateway 经 builder 按节点 session 重建 +g = StateGraph(ChainState) +g.add_node("write_chapter", write_chapter) # assemble→收集版写章(gateway.run,非SSE)→落 chapters draft +g.add_node("review_chapter", review_chapter) # 四审→落 chapter_reviews(只读留痕) +g.add_node("decide", decide) # 纯逻辑: 读最近审稿 → has_conflicts +g.add_node("accept_chapter", accept_chapter) # interrupt-on-conflict; 复用 run_accept_transaction + 伏笔扫描 +g.add_edge("write_chapter", "review_chapter"); g.add_edge("review_chapter", "decide") +g.add_conditional_edges("decide", ...) # 有冲突→interrupt()暂停交人; 无冲突→直接 accept +g.add_conditional_edges("accept_chapter", ...) # current