Files
writer-work-flow/memory/contracts.md
Yaojia Wang 5fb7bfb1de feat: M3 — 伏笔账本 + 节奏引擎 + 大纲(含并发记账 bugfix)
- 伏笔账本:纯函数状态机(OPEN/PARTIAL/CLOSED/OVERDUE) + ForeshadowLedger repo;验收后到期扫描(BackgroundTask 自建 session 置 OVERDUE);登记/状态变更端点
- 节奏 + 三审齐:foreshadow-analyst + pace-checker 并入 LangGraph 并行审(REVIEW_SPECS),collect 分列落 chapter_reviews(conflicts/foreshadow_sug/pace),review SSE 加 foreshadow/pace 事件
- 大纲:outliner Agent 产 OutlineResult(含 foreshadow_windows),POST /outline 逐章 upsert outline 表;GET /foreshadow?status= 看板
- 前端:伏笔四泳道看板(OVERDUE 琥珀) + 大纲编辑器(窗口徽标) + 节奏节拍图(▁▃▅) + 审稿页消费 foreshadow/pace SSE
- bugfix(T3.8):并行三审共用请求 session 记账触发 'Session is already flushing' → foreshadow/pace 静默丢失;SqlAlchemyLedgerSink.record 改 add-only(靠端点/事务 commit),加并发回归测试
- M3 E2E:真实 DB + mock 网关零 token 走通 埋设→进展→验收后扫描 OVERDUE→看板 + 大纲含窗口 + 三审齐 SSE/留痕;E2E 暴露并钉住上述 bug
- 门禁绿:mypy 111 / pytest 228(0 xfailed) / alembic 无漂移;前端 gen:api/lint/tsc/vitest 69/build
2026-06-18 14:21:17 +02:00

23 KiB
Raw Blame History

契约登记(解耦缝)

跨 agent 的契约——多 agent 靠它解耦并行。契约先行:定义方先在此登记并标 稳定,依赖方才动工;改契约必须在此记一笔 + 在 PROGRESS.md 通知依赖任务(前端要重生成 TS 客户端、依赖模块要重同步)。 状态:待定义草拟(@skill)稳定已变更(见日志)


C1 · LLM 网关接口 owner @llm 状态: 稳定T1.1, 2026-06-18

  • 来源:ARCHITECTURE.md §4.1LlmRequest / LlmResponse / Block / Usagesnake_case)。
  • 消费方:编排器、所有 AgentGateway.run / Gateway.stream)。
  • 关键不变量agent 只传 tierwriter/analyst/light不传具体 model。
  • 已实现(packages/llm_gateway/ww_llm_gateway
    • 类型 types.pyBlock(text,cache=False)Scope(user_id,project_id?)LlmRequest(tier,input:str|list[Block],system:list[Block],stream,output_schema?,thinking,max_tokens?,scope)Usage(provider,model,input_tokens,output_tokens,cache_read_tokens,cost_minor,currency)ServedBy(provider,model,fell_back)LlmResponse(text,parsed?,usage,served_by)Delta(text)Tier
    • Gateway(adapters:dict[str,ProviderAdapter], ledger:LedgerSink, resolver=resolve_route)async run(req)->LlmResponsestream(req)->AsyncIterator[Delta]。每次调用落 1 条 usage_ledger(经 LedgerSink,可注入内存替身)。
    • 适配器 ProviderAdapter(Protocol)providercapabilities()->Capabilitiesasync complete(req,model)->ProviderResultstream(req,model)->AsyncIterator[StreamChunk]。M1 实现 OpenAICompatAdapter(provider, client:AsyncOpenAI)DeepSeek注入 client 便于测试)。
    • 档位路由 resolve_route(tier)->Route(provider,model)config.tier_defaultsM1 仅全局默认;回退/熔断属 M5/T5.4未实现)。
    • 记账 SqlAlchemyLedgerSink(session)UsageLedgerowner_id=scope.user_id 单用户 stub列名 cache_read)。成本表 pricing.py(未知 provider/model→0

C2 · DB schemaSQLAlchemy 模型 owner @db 状态: 待定义

  • 来源:ARCHITECTURE.md §3.1 DDL11 创作表 + chapter_reviews + 运营表无向量列users stub
  • 消费方:@backend(Repository/记忆服务/验收)、@llm(Agent reads/writes)。

C3 · API / OpenAPI 端点 owner @backend 状态: 稳定M1 端点全落T1.7 settings + T1.4 projects/chapters, 2026-06-18

  • 来源:ARCHITECTURE.md §7.2 端点清单(章节端点统一 /projects/:id/chapters/:no/...;含 PUT .../draft(自动保存)、/refine/jobs/:id/reviews)。
  • 消费方:@frontend(经 OpenAPI→TS 客户端)。改端点/字段 → 前端必须 pnpm gen:api 重生成客户端。
  • 已落T1.7, 2026-06-18snake_case响应仅脱敏 key
    • GET /settings/providersProvidersResponse{ providers:[ProviderView{provider, masked_key}], tier_routing:[TierRoutingView{tier, provider, model, fallback:list[str]}] }
    • PUT /settings/providersProvidersUpsertRequest{ credentials:[ProviderCredentialInput{provider, api_key}], tier_routing:[TierRoutingInput{tier, provider, model, fallback}] }ProvidersResponse(脱敏)
    • POST /settings/providers/testTestConnectionRequest{provider}TestConnectionResponse{provider, ok, capabilities:CapabilitiesView{structured_output, prefix_cache, thinking}}
    • 明文 key 永不过边界probe 经可注入 ProviderProbe(测试用 fake不联网
  • 已落T1.4, 2026-06-18snake_case
    • POST /projectsProjectCreateRequest{title, genre?, logline?, premise?, theme?, selling_points:list, structure?} → 201 ProjectResponse{id, title, genre?, logline?, premise?, theme?, selling_points, structure?}
    • GET /projectsProjectListResponse{projects:[ProjectResponse]}GET /projects/{id}ProjectResponse404 NOT_FOUND 信封)
    • POST /projects/{id}/chapters/{no}/draftSSE text/event-stream,帧 event:<token|done|error>\ndata:<json>\n\ntoken{text}/done{length}/error{code,message,request_id});无凭据 → 流前 LLM_UNAVAILABLE(503) JSON 信封(非帧)。
    • PUT /projects/{id}/chapters/{no}/draftDraftSaveRequest{text} → 200 DraftResponse{project_id, chapter_no, volume, status, version, length}幂等version 固定 1、status='draft')。
    • 网关注入缝 get_writer_gateway(从凭据构 OpenAICompatAdapter+SqlAlchemyLedgerSink);测试经 app.dependency_overrides 注 mock 网关(只需 .stream(req))。
  • @frontend 行动M1 端点已全在 OpenAPI → Wave D 前先 cd apps/web && pnpm gen:api 重生成 lib/api/schema.d.ts
  • 已落T2.4+T2.5, 2026-06-18snake_case全部挂 projects.router、已在 OpenAPI— M2 审/裁/验收三端点
    • POST /projects/{id}/chapters/{no}/reviewReviewRequest{draft?}(空/缺→回退已存草稿无草稿→404 NOT_FOUND)→ SSE text/event-stream,帧:section{name,status:"done"|"incomplete"}(每审一条M2 仅 continuity) / conflict{type,where,refs:list,suggestion}(每冲突一条,形=C6 Conflict 五类) / done{length=审项数} / error{code,message,request_id}。无凭据→流前 LLM_UNAVAILABLE(503 JSON 信封)。续审网关 tier=analystget_review_gateway)。端点流耗尽后 session.commit()(网关 ledger + collect 均只 flush
    • GET /projects/{id}/chapters/{no}/reviewsReviewHistoryResponse{reviews:[ReviewHistoryItem{id,project_id,chapter_no,chapter_version?,conflicts:list,foreshadow_sug:list,style?,pace?,health_score?,decisions?}]}(新→旧)。
    • POST /projects/{id}/chapters/{no}/acceptAcceptRequest{final_text(min1), decisions:[ConflictDecision{conflict_index:int>=0, verdict:"accept"|"ignore"|"manual", note?}]}AcceptResponse{project_id,chapter_no,accepted_version:int,digest_added:bool,decisions_recorded:int,review_id?:uuid}冲突 gate:裁决的 conflict_index 集合须覆盖 range(len(最近一条 review.conflicts))缺判→409 CONFLICT_UNRESOLVED + details.missing_conflict_indices/conflict_count无留痕或零冲突→直通。digest 提炼 tier=lightget_digest_gateway在事务外R2单事务 promote(R4)+digest.append(#4)+set_decisions 末尾一次 commitR3
    • 注入缝:build_gateway_for_tier(session, store, tier)(原 build_writer_gateway 退化为 writer 特例) + get_review_gateway/get_digest_gateway/get_review_repo/get_digest_append_repo;测试经 app.dependency_overrides 注 mock。
    • @frontend 行动cd apps/web && pnpm gen:api 重生成客户端含此三端点T2.6)。
  • 已落T3.2, 2026-06-18snake_case— 伏笔登记/状态端点 + 验收后到期扫描(挂 foreshadow.routerprefix /projectstag foreshadow,已注册):
    • POST /projects/{id}/foreshadowForeshadowRegisterRequest{code(min1),title(min1),planted_at?,content?,expected_close_from?,expected_close_to?,importance?} → 201 ForeshadowView{code,title,status,planted_at?,content?,expected_close_from?,expected_close_to?,importance?,links:list,progress:list}(status=OPEN)。重复 code(DB 唯一 (project_id,code))→ 422 VALIDATION+details{field:"code",code,reason:"duplicate"}
    • PATCH /projects/{id}/foreshadow/{code}ForeshadowTransitionRequest{to_status?,progress_entry?} → 200 ForeshadowView。非法转移→422 VALIDATION+details{reason:"invalid_transition"}二者都缺→422 empty_updatecode 不存在→404 NOT_FOUND。先转移后追加进展,端点写后 commit()(repo 只 flush)。
    • 错误码取舍:重复/非法转移映射 VALIDATION(422) 非 409——现有唯一 409 码 CONFLICT_UNRESOLVED 语义专指未决冲突禁验收,不复用;未动 packages/shared/errors.py 契约。若 T3.5/前端需真 409 须先加专用码。
    • 验收后到期扫描POST .../accept 单事务 commit 成功后经 BackgroundTasks 登记 run_overdue_scan(session_factory, project_id, chapter_no=刚验收章号)——自建独立 session(请求 session 已关闭)、应用 current_ch>expected_close_to AND status≠CLOSED→OVERDUE、有变更才 commit日志 foreshadow_overdue_scan(带 request_id/overdue_count/codes)失败吞不冒泡。§7.4 持久性局限(进程内/重启丢任务)原型接受、不引 jobs 表。
    • T3.5 续接GET /projects/{id}/foreshadow?status= 看板加到本 foreshadow.router(注入 get_foreshadow_repolist_by_status,建议响应包 {foreshadow:[...]}POST .../outline独立 routers/outline.py(别混进 foreshadow.py),调 run_outline(C6 扩)+build_gateway_for_tier(...,"analyst"),逐章 upsert outline 表、端点末尾 commit。
    • @frontend 行动T3.6 前 pnpm gen:api 纳入伏笔登记/状态端点(+ T3.5 看板/outline
  • 已落T3.5, 2026-06-18snake_case— 伏笔看板 + 大纲生成端点
    • GET /projects/{id}/foreshadow?status=tag foreshadowquery status?OPEN|PARTIAL|CLOSED|OVERDUE(缺省=全部非法→422 VALIDATION+details{field:"status",reason:"invalid_status"}) → 200 ForeshadowBoardResponse{foreshadow:list[ForeshadowView]}(按 code 升序)。
    • POST /projects/{id}/outlinetag outline,独立 routers/outline.py)← OutlineGenerateRequest{volume:int=1,ge=1}M3 简单分卷:整批同卷)→ 200 OutlineResponse{chapters:list[OutlineChapterView{no,volume,beats:list[str],foreshadow_windows:list[ForeshadowWindowView{code,plant_chapter?,expected_close_from?,expected_close_to?}]}]}项目不存在→404 NOT_FOUND;无凭据/网关失败→503 LLM_UNAVAILABLE(流前 JSON 信封)。调 run_outline(analyst 网关 get_outline_gateway)→逐章 upsert outline 表(写侧 SqlOutlineWriteRepo,只 flush)→端点末尾 commit()(含 1 条 usage_ledger
    • beats 存储形DB outline.beats 是 JSONB dict→写侧包成 {"beats":[...]} 落库API 出参 OutlineChapterView.beats 解包成裸 list[str]
    • @frontend 行动T3.6 pnpm gen:api 纳入看板/大纲端点。

C4 · 编排器接口LangGraph 写章图 owner @llm 状态: 稳定M1/T1.3, 2026-06-18M2 扩四审/验收)

  • 来源:ARCHITECTURE.md §5.2(图)/ §7.3SSEM1 仅单 write 节点;并行四审/collect/interrupt(accept) 属 M2。
  • 位置:packages/core/ww_core/orchestrator/
  • 图状态 ChapterState(TypedDict, snake_case){project_id:UUID, chapter_no:int, user_id:UUID, stable_core:str, volatile:str, draft:str}——仅控制流+组装上下文+累积草稿(不变量#5resume 从领域表重读。
  • 节点缝:
    • build_write_request(*, stable_core, volatile, user_id, project_id) -> LlmRequest(纯函数;tier="writer"system=[Block(stable_core,cache=True)]input=volatilestream=Truescope=Scope(user_id,project_id))。
    • async stream_chapter_draft(gateway, *, stable_core, volatile, user_id, project_id) -> AsyncIterator[Delta]——T1.4 拿来喂 normalize_deltas 的底层流缝
    • async write_node(state, *, gateway) -> {"draft":str}——可直接单测(注入 mock 网关)。GatewayStream Protocol = 节点对网关最小依赖(只需 .stream(req))。
  • SSE 归一缝T1.4 消费)async normalize_deltas(deltas, *, request_id=None) -> AsyncIterator[SseEvent]SseEvent{event:str, data:dict};事件 token{text}/done{length}/error{code,message,request_id}ARCH §7.3 子集M2 加 section/conflict。底层异常→发 error 事件后收尾(不上抛);AppError.code 透传,未知→INTERNAL。HTTP event-stream 编码归 T1.4。
  • 图工厂:build_write_graph(gateway, *, checkpointer=None) -> CompiledStateGraphSTART→write→END单测传 MemorySaver
  • checkpointer setup 入口:async setup_checkpointer(conn_string) -> None——只在 migrations/CI 调(内部 AsyncPostgresSaver.from_conn_string 跑一次 setup() DDL懒 import绝不在 app-runtime 跑)。
  • 不变量agent 只传 tierDB 唯一 agent 间通道;不可变更新;瞬时重试在网关不在节点。

C4 扩展T2.2, 2026-06-18· 审稿子图 + review SSE

  • 图工厂 build_review_graph(gateway, review_repo, *, review_specs=(continuity_spec,), checkpointer=None) -> CompiledStateGraphSTART →各 review spec 并行节点→ collect → END。M2 默认仅 continuityreview_specs 可扩M3/M4 加 foreshadow/style/pacebuild_write_graph 保留不动draft 端点仍用它)。
  • ChapterState(state.py) 新增 review_context:strreviews:Annotated[dict, merge_reviews](并行分支浅合并 reducer返回新 dictTypedDict 改 total=False(按节点逐步填充);仍仅控制流+组装上下文+产物句柄(不变量#5
  • 节点缝:run_review(spec, state, *, gateway)->{"reviews":{spec.name:{status,result}}}(裸函数可单测);make_review_node(spec, gateway)(绑 gateway 的公共缝,供 T2.5 跑单审);审稿用 gateway.run()(非 stream只读不写库(不变量#3任一审网关失败被隔离为 {status:"incomplete",result:None}§5.2),不上抛、不毁图。GatewayRun Protocol=节点对网关最小依赖(.run(req)->LlmResponse)。
  • collect 缝:collect_reviews(state, *, review_repo)->{}:抽 continuity 冲突 → review_repo.record(project_id, chapter_no, chapter_version=None, conflicts=[...])chapter_reviews 留痕;只 flush 不 commit(提交归端点/T2.4 事务)。ReviewRecorder Protocol 形对齐 domain.review_repo.ReviewRepo.record
  • SSE 新增事件:section{name,status}status ∈ started/done/incompleteconflict{type,where,refs,suggestion}(对齐 C6 Conflict);新归一缝 normalize_review(reviews, *, request_id=None)->AsyncIterator[SseEvent](每审一条 section + 每冲突一条 conflict + done{length=审项数};异常→error 不上抛,同 normalize_deltas 纪律。HTTP event-stream 编码归 T2.5。
  • accept 不在本图interrupt_before=["accept"]/accept 节点属 T2.4确定性事务代码从领域表重读R3/不变量#5
  • 消费方行动T2.5 跑 review 子图 + normalize_review + 端点流耗尽后 await session.commit()(网关 ledger + collect 均只 flush不提交则记账/留痕静默丢失,同 M1 draft 坑);GET .../reviewsreview_repo.list_for_chapter(新→旧)。T2.4 从 review_repo.list_for_chapter 读 collect 落的行(decisions=None)裁决。

C4 扩展T3.3, 2026-06-18· 三审齐foreshadow + pace 并入)

  • REVIEW_SPECS = (continuity_spec, foreshadow_spec, pace_spec)build_review_graph(..., review_specs=REVIEW_SPECS) 默认三审齐(文风第四审留 M4run_review/make_review_node 对 spec 泛型(按 req.output_schema 产 parsed
  • collect 列映射(一次 review_repo.record(...) 落齐continuity→conflicts(list)foreshadow→foreshadow_sug(listplanted/resolved 扁平成单 list、每条加 kind:"planted"|"resolved")pace→pace(dict{water,hook,beat_map} 入列)style 留 M4。任一审 incomplete→该列空/None不阻塞其余§5.2)。
  • normalize_review SSE向后兼容section/conflict 不变)新增:foreshadow{kind,code?,title,where?,note?}(每条建议一条)、pace{water:[{where,reason}],hook:bool,beat_map:[int]}一条。键按字典序确定性遍历continuity<foreshadow<pace。incomplete 审仅发 section 不发结果。
  • 消费方:前端 T3.6(冲突就地标注 / 伏笔看板联动 / 节拍图 ▁▃▅、E2E T3.7。

C5 · 记忆服务 assemble / select_relevant_entities owner @backend 状态: 稳定T1.2, 2026-06-18

  • 来源:ARCHITECTURE.md §3.4 / §5.3(确定性选择:显式+主角+近况;渲染卡片;缓存断点)。
  • 位置:packages/core/ww_core/memory/+ domain/repositories.py)。
  • 输出(决策:中性文本,非 LlmRequest,见 decisions 2026-06-18
    • AssembledContext{ stable_core:str, volatile:str, selection:SelectionTrace }
      • stable_core:断点前(世界硬规则+定型主角(无 latest_state)+文风指纹+合并规则),已排序/无时间戳/无 UUID。
      • volatile:断点后(注入卡片(含 latest_state)+伏笔窗口+近况摘要+本章 beats
    • SelectionTrace{ selected:list[SelectedEntity] }SelectedEntity{ kind:"character"|"world_entity", name:str, reasons:list[SelectionReason] }SelectionReason="explicit_beat"|"main_character"|"recent_digest"|"foreshadow_window"
  • 入口:async assemble(repos:MemoryRepos, project_id, chapter_no, recent_k=5)->AssembledContext;纯函数 select_relevant_entities(*, outline, characters, world_entities, recent_digests)->SelectionTracerender_cards(selection, characters, world_entities)->strmerge_rules(rules)->list[RuleView]global→genre→style→project
  • 依赖注入:MemoryRepos 捆绑 7 个 ProtocolOutline/Character/WorldEntity/Digest/Foreshadow/Style/Rules单测注入内存 fake运行时 sql_memory_repos(AsyncSession)T1.4 注入点)。
  • 不变量:确定性选择(无向量 #6);统一 project_id 过滤(§3.5)latest_state 严格归 volatile(#9)。
  • 消费方:T1.3 write 节点 → 构造 LlmRequest(system=[Block(stable_core, cache=True)], input=volatile)

C6 · Agent 声明AgentSpec+ 续审 I/O schema owner @llm 状态: 稳定T2.1, 2026-06-18

  • 来源:ARCHITECTURE.md §5.1 / §5.4 / §6.1。位置:packages/agents/ww_agents/specs.py / schemas.py,经 ww_agents 导出)。
  • AgentSpecfrozen Pydantic不可变name:strtier:Tier(writer/analyst/light复用 ww_llm_gateway.types.Tier——只声明档位不写 model不变量#2)、system_prompt:strinput_schema:type[BaseModel]|Noneoutput_schema:type[BaseModel]|None(writer 为 None=纯文本)、reads:list[str]writes:list[str]genre:str|None=Nonescope:str="builtin"
  • continuity_spectier="analyst"reads=["chapter_digests","characters","world_entities"]writes=[](只读,不变量#3)、input_schema=None(注入材料为序列化文本)、output_schema=ContinuityReview
  • ContinuityReview{ conflicts: list[Conflict] }仅冲突digest 不在审稿期产,不变量#4
  • Conflict{ type: ConflictType, where:str, refs:list[str]=[], suggestion:str }ConflictType = Literal["性格漂移","能力不符","设定违例","地理矛盾","时间线倒错"]ARCH §6.1 五类)。
  • 续审节点调法:req = LlmRequest(tier="analyst", system=[Block(system_prompt, cache=True)], input=审稿上下文文本, output_schema=ContinuityReview, scope=...)resp = await gateway.run(req)resp.parsedContinuityReview 实例(带 schema 时必非 None。续审用 run()stream();只读+产冲突,不写库(写在 T2.4 验收事务)。
  • 消费方:编排器(T2.2 续审/collect 节点)、技能运行时(M5)、前端审稿页(经 C3)。foreshadow/style/pace 三审 spec 待 M3-T3.3/M4 补入本契约。

C6 扩展T3.4, 2026-06-18· outliner Agent + 大纲/伏笔窗口 schema

  • 位置:packages/agents/ww_agents/{schemas.py,specs.py}(经 ww_agents 导出);节点在 packages/core/ww_core/orchestrator/outline_node.py(经 orchestrator 导出)。
  • outliner_spec(frozen AgentSpec)name="outliner"tier="analyst"reads=["projects","foreshadow","characters","world_entities"]writes=["outline"](声明式,真写库经 T3.5,不变量#3)、input_schema=Noneoutput_schema=OutlineResult
  • OutlineResult{chapters:list[OutlineChapter]}OutlineChapter{no:int, beats:list[str], foreshadow_windows:list[ForeshadowWindow]}ForeshadowWindow{code:str, plant_chapter?:int, expected_close_from?:int, expected_close_to?:int}§6.2 关联章与伏笔)。
  • 节点缝(沿用 review_node 的 GatewayRun Protocol .run(req)->LlmResponsebuild_outline_request(spec, *, context, user_id, project_id)->LlmRequest(output_schema=OutlineResult,stream=False) + async run_outline(spec, *, context, gateway, user_id, project_id)->OutlineResult(gateway.run().parsed只读不写库;大纲独立生成、非并行审,网关失败直接上抛不隔离parsed 违约抛 ValueError)。
  • 消费方T3.5 端点调 run_outlineOutlineResult → 逐章 upsert outline 表(beats/foreshadow_windows 入 JSONBvolume 由端点分卷逻辑提供schema 不含 volumeanalyst 网关复用 build_gateway_for_tier(session,store,"analyst")/get_review_gateway;记账闭环同 M2端点末尾 commit()。T3.6 前端大纲编辑器消费窗口。

C6 扩展T3.3, 2026-06-18· foreshadow-analyst + pace-checker spec

  • foreshadow_spec(name="foreshadow", tier="analyst", reads=["foreshadow"], writes=[]只读, output=ForeshadowReview{planted:[ForeshadowSuggestion],resolved:[ForeshadowSuggestion]}ForeshadowSuggestion{code?,title,where?,note?})。
  • pace_spec(name="pace", tier="light", reads=["rules"], writes=[]只读, output=PaceReview{water:[PaceIssue{where,reason}],hook:bool,beat_map:[int]})。genre 模板 DSL = pace system_prompt 内描述黄金三章/章末钩子/爽点密度genre 级模板经 review_context 规则合并(rules)注入,未新建表/repo
  • 三审只读,留痕经 collect→review_repo写库 commit 归端点)。详细图/SSE 见上「C4 扩展T3.3)」。

C7 · 前端 ↔ 后端类型契约 owner @backend(产出 OpenAPI) / @frontend(生成) 状态: 待定义

  • 机制FastAPI OpenAPI → apps/web/lib/api TS 类型openapi-typescript/orval
  • 规则:后端 schema 任何变更 → 跑 gen:api 重生成;不手写共享类型。

契约变更日志append-only

格式:- [date] @skill 改 Cx<改了什么> → 影响 <依赖方/任务>

  • [2026-06-18] @llm 改 C1Gateway.run() 现消费 LlmRequest.output_schema——OpenAICompatAdapter.complete 在 schema 非空时经 instructor(create_with_completion(response_model=...)) 取已校验 Pydantic 实例并填 LlmResponse.parsed;无 schema 时 parsed is None、纯文本路径不变;记账仍 1 条 usage_ledger/调用usage 从 raw completion 提取)。ProviderResult 新增 parsed 字段。结构化路径经可注入 StructuredClient Protocol 注 fake测试不联网。→ 影响 T2.2(续审节点可直接 gateway.run(req).parsed)、未来所有结构化输出 Agent。