Files
writer-work-flow/memory/decisions.md
Yaojia Wang 68f194a043 feat: M2 — 写→审(一致性)→裁决→验收(事务);未决冲突禁验收
- 续审 Agent 声明(AgentSpec) + 结构化输出契约(ContinuityReview/Conflict 五类)
- LangGraph 并行审子图(可扩四审) + collect 落 chapter_reviews 留痕 + review SSE(section/conflict)
- 验收-side Repository:章节 accepted 版本晋升 + digest append-only + 审稿留痕/裁决
- API:review(SSE) + reviews 历史 + accept(单原子事务:晋升 version + 终稿 digest + 裁决留痕)
- 冲突 gate:未决裁决拦截(CONFLICT_UNRESOLVED);digest 从终稿提炼(不变量#4)
- 前端:审稿报告页 + 冲突就地标注 + 裁决(采纳/忽略/手改) + 未决禁验收 + 「本次将更新」清单
- M2 E2E:真实 DB + 多档位 mock 网关零 token 走通 写→审→裁决→验收→摘要入库
- 多 agent 协同台账(PROGRESS.md) + 共享记忆(memory/contracts·decisions·gotchas)
2026-06-18 11:38:28 +02:00

50 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 实现决策记录append-only
> 实现期做出、而规格未覆盖的决策 + 理由。一条一决策,最新在最上。
> 与规格冲突的不要写这里——去改规格(见 CLAUDE.md「Conventions」。重大架构选择见 `ARCHITECTURE.md §1.2 ADR`。
格式:
```
## [YYYY-MM-DD] <决策标题> — @skill
- 背景:为什么要决策
- 选择:选了什么
- 理由 / 取舍:
- 影响:动到哪些契约/模块/任务
```
---
## [2026-06-18] 网关结构化输出经可注入 `StructuredClient` 缝instructor— @llm
- 背景:`OpenAICompatAdapter` 需接 instructor 产结构化输出C1 类型预留 `output_schema`/`parsed` 但 M1 未接线),但测试绝不可联网/碰真实 LLM。
- 选择:定义 `StructuredClient` Protocol`create_with_completion(*, messages, response_model, **kw) -> (parsed, raw)`,即 `instructor.AsyncInstructor` 的形adapter 构造可选注入 `structured_client`,未注入时懒构建 `instructor.from_openai(self._client)``run()` 透传 `result.parsed → LlmResponse.parsed`
- 理由 / 取舍:保留既有「注入 client 便于测试」风格,结构化路径同样可注 fake。`ProviderResult.text` 对结构化路径置为 `parsed.model_dump_json()`(便于日志/留痕),程序消费走 `parsed`
- 影响:契约 C1`run()``parsed`;见 contracts 变更日志)、`ProviderResult``parsed` 字段T2.2 续审节点据此消费。
## [2026-06-18] 审稿页终稿来源:页内可编辑 textarea无 GET draft 端点)— @frontend (T2.6)
- 背景:审稿/验收都需「终稿」文本,但当前无 `GET .../draft` 端点拉取已存草稿。
- 选择:审稿页用可折叠 textarea 编辑终稿(`initialDraft=''`),重审传它为 `{draft}`(空→后端回退已存草稿),验收 `final_text`=它。
- 理由 / 取舍:对齐不变量#4(摘要从作者裁决/改稿后的终稿提炼作者可在审稿页改稿。代价刷新页不自动回填草稿正文M2 可接受,后续可加 GET draft
- 影响仅前端T2.7 E2E 用 `#final-text` 注入终稿。
## [2026-06-18] 验收事务边界digest 提炼在事务外、单事务三写一次提交 — @backend (T2.4)
- 背景§5.5 要求验收「单事务」含「从终稿提炼 digestLLM 调用)」,但不能在持开 DB 事务里跨网络调 LLM占锁
- 选择digest 提炼 `extract_digest_facts`(tier=light`ChapterDigestFacts` schema经网关 `run().parsed`) 在 `run_accept_transaction` **之前**调用R2结果作 `digest_facts:dict` 传入;事务内只三次纯 DB 写(`promote_to_accepted``digest.append``set_decisions`+ 末尾一次 `session.commit()`,任一步失败整体回滚。
- 理由 / 取舍兼顾「digest 从终稿(不变量#4)」与「事务不跨网络」;提炼那次网关调用的 usage 随事务提交落 1 条 usage_ledger。digest 用 light、续审用 analyst 档位,统一经新 `build_gateway_for_tier(session, store, tier)``build_writer_gateway` 退化为 writer 特例。§5.5 步骤 3人物 latest_state/伏笔更新)留 `TODO(M3)` 占位,不引入 M3 表逻辑。
- 影响:契约 C3accept/review/reviews 三端点T2.6 消费 `AcceptResponse`/`CONFLICT_UNRESOLVED`T2.7 E2E 验证事务原子性+digest 来源+ledger。
## [2026-06-18] 验收-side 写侧 Repository 命名与提交边界 — @backend (T2.3)
- 背景:`chapter_digests` 已有读侧 repo`memory/``DigestRepo.recent`,供 assembleM2 需写侧 append。同名易混。
- 选择:写侧命名 `DigestAppendRepo`/`SqlDigestAppendRepo`,落 `domain/digest_repo.py`**共用 `DigestView`**;读侧仍在 `memory/`。新增 `review_repo.py`(`record`/`list_for_chapter` 新→旧/`set_decisions`) 与 `chapter_repo``max_version`/`promote_to_accepted`/`latest_accepted`
- 理由 / 取舍:读写分离避免同名歧义;写侧三 repo 的写方法**只 `flush()``commit()`**,提交归 T2.4 验收事务单次完成(对齐「写库副作用在编排/事务层」不变量);`save_draft` 仍自 commitM1 自动保存语义)。
- 影响T2.4 在单事务里组合 `promote_to_accepted``digest.append``review.set_decisions``commit()`R2/R3/R4/R5 决策落在这些接口点。
- 背景ARCH §5.3 伪码里 `assemble` 直接返回 `LlmRequest`,会让 `packages/core/memory` 反向依赖 `packages/llm_gateway` 的类型,且使 M1 的 T1.2 强依赖 T1.1(无法并行)。
- 选择:`assemble(project_id, chapter_no) -> AssembledContext(stable_core: str, volatile: str, selection: SelectionTrace)`。稳定内核/易变各为**已确定性排序、无时间戳/UUID 的字符串**write 节点(T1.3)再据此构造 `LlmRequest(system=[Block(text=stable_core, cache=True)], input=volatile)``SelectionTrace` 记录每个实体的入选理由,供 UX 注入透明面板(T1.6)。
- 理由 / 取舍:记忆服务只经 DB 通信、不应知道网关请求类型(对齐不变量①②);解耦后 T1.1‖T1.2 可并行。§5.3 伪码视为示意,真正的缝是「确定性选择 → 序列化的 stable/volatile 文本」。
- 影响:契约 C5 输出形 = `AssembledContext`(非 `LlmRequest`C1 的 `Block`/`LlmRequest` 仅 gateway+orchestrator 使用。回写 ARCH §5.3 待 M1 落地后由 @docs 注一句。
## [2026-06-18] CLAUDE.md 不复制 spec 的枚举值,只重述跨文档规则 — @all
- 背景CLAUDE.md 原先内联了 SSE 事件名、LLM 调用日志字段、错误码/envelope 形状等"会变的具体值"ARCHITECTURE 一改即静默过期,使 CLAUDE.md 自身成为最大漂移源。
- 选择CLAUDE.md 只保留跨文档、易错的**规则**(含 9 条架构不变量);所有枚举型/具体值改为指向 ARCHITECTURE/PRODUCT_SPEC 对应 §(唯一真源)。新增"冲突裁决"段ARCHITECTURE > UX_SPEC/DEV_PLAN > PRODUCT_SPEC矛盾时回写上游。
- 理由 / 取舍:消除文档间漂移面、降低每 session 上下文成本;代价是查具体值需多跳一层到 ARCHITECTURE可接受
- 影响:编辑 CLAUDE.md 的任何 agent 遵循此纪律——契约/枚举值落在 spec 与 `memory/contracts.md`,不在 CLAUDE.md。