"""Agent 声明式抽象(ARCH §5.1)+ 续审 Agent 实例(Prompt 外置方案A · 步3/5)。 `AgentSpec` 是内置 Agent 与用户 Skill 的同构声明:一份只读声明,由编排器加载、 经网关按 `tier` 执行。不变量 #2:agent 只声明 `tier`,**不**写具体 model。 不变量 #3:四审 `writes=()`(只读),任何 AI 产出入库必经验收事务。 prompt 散文已外置至 `prompts/.md`:`system_prompt=load_prompt(name)` import 期读盘+缓存+规整(去BOM/LF/NFC/rstrip尾LF/fail-fast)。output schema 由 `SCHEMA_CATALOG[name]` 单向派生(唯一真相源)。`AgentSpec` 从 `spec_model` re-export (import 路径不变,现有消费方不受影响)。 不可变:`AgentSpec` 为 frozen Pydantic 模型——加载后不得改动。 """ from __future__ import annotations from collections.abc import Mapping from types import MappingProxyType from typing import Final from .prompt_loader import load_prompt from .schema_catalog import SCHEMA_CATALOG from .spec_model import AgentSpec __all__ = [ "REVIEW_RESERVED_NAMES", "SPECS", "AgentSpec", "blurb_spec", "book_title_spec", "brainstorm_spec", "character_gen_spec", "characterization_spec", "continue_spec", "continuity_spec", "de_ai_spec", "expand_spec", "fine_outline_spec", "foreshadow_spec", "glossary_spec", "golden_finger_spec", "name_spec", "opening_spec", "outliner_spec", "pace_spec", "project_plan_spec", "refiner_spec", "style_drift_spec", "style_extract_spec", "teardown_spec", "worldbuilder_spec", ] continuity_spec = AgentSpec( name="continuity", tier="analyst", system_prompt=load_prompt("continuity"), input_schema=None, # 注入材料为序列化文本(经记忆 assemble),非结构化入参 output_schema=SCHEMA_CATALOG["continuity"], reads=("chapter_digests", "characters", "world_entities"), writes=(), # 只读(不变量 #3) scope="builtin", ) outliner_spec = AgentSpec( name="outliner", tier="analyst", # 不变量 #2:只声明档位,不写 model system_prompt=load_prompt("outliner"), input_schema=None, # 注入材料为序列化文本(设定/伏笔/人物/世界观),非结构化入参 output_schema=SCHEMA_CATALOG["outliner"], reads=("projects", "foreshadow", "characters", "world_entities"), writes=("outline",), # 声明式(经验收/T3.5 才真写库,不变量 #3) scope="builtin", ) foreshadow_spec = AgentSpec( name="foreshadow", tier="analyst", # 不变量 #2:只声明档位,不写 model system_prompt=load_prompt("foreshadow"), input_schema=None, # 注入材料为序列化文本(已登记伏笔 + 草稿),非结构化入参 output_schema=SCHEMA_CATALOG["foreshadow"], reads=("foreshadow",), writes=(), # 只读(不变量 #3) scope="builtin", ) pace_spec = AgentSpec( name="pace", tier="light", # 不变量 #2:只声明档位,不写 model(节奏审用轻量档) system_prompt=load_prompt("pace"), input_schema=None, # 注入材料为序列化文本(题材模板 + 草稿),非结构化入参 output_schema=SCHEMA_CATALOG["pace"], reads=("rules",), # genre 模板 DSL 经 rules(genre 级)注入,复用 review_context 规则合并 writes=(), # 只读(不变量 #3) scope="builtin", ) # ---- 文风提取轨(style-auditor「提取」轨;analyst 档;独立生成,仿 outliner,不进 review 图)---- style_extract_spec = AgentSpec( name="style_extract", tier="analyst", # 不变量 #2:只声明档位,不写 model system_prompt=load_prompt("style_extract"), input_schema=None, # 注入材料为序列化样本文本,非结构化入参 output_schema=SCHEMA_CATALOG["style_extract"], reads=("style_fingerprint",), writes=("style_fingerprint",), # 声明式(真写库经 T4.3 端点,不变量 #3) scope="builtin", ) # ---- 文风漂移轨(style-auditor「打分」轨 = 第四审;light 档;并入 REVIEW_SPECS)---- style_drift_spec = AgentSpec( name="style", # 第四审 section/列名 = "style" tier="light", # 不变量 #2:只声明档位,不写 model(漂移打分用轻量档) system_prompt=load_prompt("style"), input_schema=None, # 注入材料为序列化文本(指纹 + 草稿),非结构化入参 output_schema=SCHEMA_CATALOG["style"], reads=("style_fingerprint",), # 指纹经 assemble 的 stable_core 注入 review_context writes=(), # 只读(不变量 #3) scope="builtin", ) # ---- 人物塑造续审(灵感③;analyst 档;advisory 单维——仅建议、不阻断验收,D2)---- characterization_spec = AgentSpec( name="characterization", tier="analyst", # 不变量 #2:只声明档位,不写 model(人物塑造诊断用分析档) system_prompt=load_prompt("characterization"), input_schema=None, # 注入材料为序列化文本(人物卡 + 草稿,经 assemble),非结构化入参 output_schema=SCHEMA_CATALOG["characterization"], reads=( "characters", ), # 以人物卡 motive/appearance 为客观锚点(经 assemble 的 stable_core 注入) writes=(), # 只读(不变量 #3);advisory 唯一落库=collect 留痕,不进 conflicts/验收 gate scope="builtin", ) # ---- 回炉(refiner;writer 档;纯文本重写;非 Agent 流水线、非持久,M4-e)---- refiner_spec = AgentSpec( name="refiner", tier="writer", # 不变量 #2:重写正文用 writer 档 system_prompt=load_prompt("refiner"), input_schema=None, # 注入材料为序列化文本(选中段 + 指令 + 上下文) output_schema=SCHEMA_CATALOG["refiner"], # 纯文本产出(重写段),无结构化 schema reads=(), writes=(), # 非持久(不变量 #3):端点同步返回 {original, refined},不写库 scope="builtin", ) # ---- worldbuilder(世界观设计师;写手档;独立生成,仿 outliner,不进 review 图)---- worldbuilder_spec = AgentSpec( name="worldbuilder", tier="writer", # 不变量 #2:只声明档位,不写 model(世界观创意用写手档) system_prompt=load_prompt("worldbuilder"), input_schema=None, # 注入材料为序列化文本(设定 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["worldbuilder"], reads=("projects",), writes=("world_entities",), # 声明式(真写库经 T5.2 入库端点,不变量 #3) scope="builtin", ) # ---- character-gen(角色设计师;写手档;单/批量;群像防雷同;独立生成)---- character_gen_spec = AgentSpec( name="character-gen", tier="writer", # 不变量 #2:只声明档位,不写 model(角色创意用写手档) system_prompt=load_prompt("character-gen"), input_schema=None, # 注入材料为序列化文本(需求 + 约束 + 已有 + 已生成),非结构化入参 output_schema=SCHEMA_CATALOG["character-gen"], reads=("world_entities", "characters"), writes=("characters",), # 声明式(真写库经 T5.2 入库端点 + continuity 校验,不变量 #3) scope="builtin", ) # ---- brainstorm(脑洞生成器;轻量档;创作工具箱通用框架首个最简生成器,T6)---- brainstorm_spec = AgentSpec( name="brainstorm", tier="light", # 不变量 #2:只声明档位,不写 model(脑洞发散用轻量档) system_prompt=load_prompt("brainstorm"), input_schema=None, # 注入材料为序列化文本(作品设定 + 一句话需求),非结构化入参 output_schema=SCHEMA_CATALOG["brainstorm"], reads=("projects",), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- book-title(书名生成器;轻量档;纯预览,T6 创作工具箱)---- book_title_spec = AgentSpec( name="book-title", tier="light", # 不变量 #2:只声明档位,不写 model(书名发散用轻量档) system_prompt=load_prompt("book-title"), input_schema=None, # 注入材料为序列化文本(作品设定 + 一句话需求),非结构化入参 output_schema=SCHEMA_CATALOG["book-title"], reads=("projects",), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- blurb(简介生成器;分析档;纯预览,T6 创作工具箱)---- blurb_spec = AgentSpec( name="blurb", tier="analyst", # 不变量 #2:只声明档位,不写 model(文案打磨用分析档) system_prompt=load_prompt("blurb"), input_schema=None, # 注入材料为序列化文本(作品设定 + 一句话需求),非结构化入参 output_schema=SCHEMA_CATALOG["blurb"], reads=("projects",), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- name(取名生成器;轻量档;纯预览,T6 创作工具箱)---- name_spec = AgentSpec( name="name", tier="light", # 不变量 #2:只声明档位,不写 model(取名发散用轻量档) system_prompt=load_prompt("name"), input_schema=None, # 注入材料为序列化文本(设定 + 世界观 + 已有角色 + 需求) output_schema=SCHEMA_CATALOG["name"], reads=("projects", "world_entities", "characters"), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- golden-finger(金手指生成器;写手档;声明式 writes=world_entities,T6 创作工具箱)---- golden_finger_spec = AgentSpec( name="golden-finger", tier="writer", # 不变量 #2:只声明档位,不写 model(力量体系创意用写手档) system_prompt=load_prompt("golden-finger"), input_schema=None, # 注入材料为序列化文本(设定 + 世界观 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["golden-finger"], reads=("projects", "world_entities"), writes=("world_entities",), # 声明式(真写库经入库端点,不变量 #3) scope="builtin", ) # ---- glossary(术语表生成器;分析档;声明式 writes=world_entities,T6 创作工具箱)---- glossary_spec = AgentSpec( name="glossary", tier="analyst", # 不变量 #2:只声明档位,不写 model(术语梳理用分析档) system_prompt=load_prompt("glossary"), input_schema=None, # 注入材料为序列化文本(设定 + 世界观 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["glossary"], reads=("projects", "world_entities"), writes=("world_entities",), # 声明式(真写库经入库端点,不变量 #3) scope="builtin", ) # ---- opening(开篇生成器;写手档;纯预览,T6 创作工具箱)---- opening_spec = AgentSpec( name="opening", tier="writer", # 不变量 #2:只声明档位,不写 model(正文创作用写手档) system_prompt=load_prompt("opening"), input_schema=None, # 注入材料为序列化文本(设定 + 大纲 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["opening"], reads=("projects", "outline"), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- fine-outline(细纲生成器;分析档;声明式 writes=outline,T6 创作工具箱)---- fine_outline_spec = AgentSpec( name="fine-outline", tier="analyst", # 不变量 #2:只声明档位,不写 model(细纲拆解用分析档) system_prompt=load_prompt("fine-outline"), input_schema=None, # 注入材料为序列化文本(设定 + 本章大纲 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["fine-outline"], reads=("projects", "outline"), writes=("outline",), # 声明式(真写库经入库端点,不变量 #3) scope="builtin", ) # ---- continue(续写生成器;写手档;纯预览,Scope B 竞品快赢)---- continue_spec = AgentSpec( name="continue", tier="writer", # 不变量 #2:只声明档位,不写 model(正文续写用写手档) system_prompt=load_prompt("continue"), input_schema=None, # 注入材料为序列化文本(设定 + 前文 + 节拍 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["continue"], reads=("projects", "outline", "chapters"), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- expand(扩写生成器;写手档;纯预览,Scope B 竞品快赢)---- expand_spec = AgentSpec( name="expand", tier="writer", # 不变量 #2:只声明档位,不写 model(正文扩写用写手档) system_prompt=load_prompt("expand"), input_schema=None, # 注入材料为序列化文本(设定 + 原文 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["expand"], reads=("projects",), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- de-ai(降 AI 率生成器;分析档;纯预览,Scope B 竞品快赢)---- de_ai_spec = AgentSpec( name="de-ai", tier="analyst", # 不变量 #2:只声明档位,不写 model(文风改写用分析档) system_prompt=load_prompt("de-ai"), input_schema=None, # 注入材料为序列化文本(设定 + 原文 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["de-ai"], reads=("projects",), writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # ---- teardown(拆书生成器;分析档;纯预览,Scope B 竞品快赢)---- teardown_spec = AgentSpec( name="teardown", tier="analyst", # 不变量 #2:只声明档位,不写 model(拆解分析用分析档) system_prompt=load_prompt("teardown"), input_schema=None, # 注入材料为序列化文本(设定 + 样本 + 需求),非结构化入参 output_schema=SCHEMA_CATALOG["teardown"], reads=("projects",), writes=("rules",), # F1:拆书结论可落库为项目 rules(入库仍经 ingest 白名单 gate,#3) scope="builtin", ) # ---- project-plan(立项方案师;分析档;纯预览 writes=(),灵感⑤)---- project_plan_spec = AgentSpec( name="project-plan", tier="analyst", # 不变量 #2:只声明档位,不写 model(立项方案构思用分析档) system_prompt=load_prompt("project-plan"), input_schema=None, # 向导阶段 project 未建;材料为序列化的向导草稿(端点注入),非结构化入参 output_schema=SCHEMA_CATALOG["project-plan"], reads=(), # 立项前 project 未建、无库可读;种子经端点序列化注入 writes=(), # 纯预览,不写库(不变量 #3) scope="builtin", ) # 集中注册表:name → spec(同一实例,兼容期 *_spec 与 SPECS[name] 为同对象,不变量) # MappingProxyType:只读视图,运行时 `SPECS[x] = ...` / `del SPECS[x]` 抛 TypeError # (`Final` 仅静态检查,挡不住运行时变异;注册表是单一真相源,须运行时不可变)。 _SPECS_BY_NAME: dict[str, AgentSpec] = { s.name: s for s in ( continuity_spec, outliner_spec, foreshadow_spec, pace_spec, characterization_spec, style_extract_spec, style_drift_spec, refiner_spec, worldbuilder_spec, character_gen_spec, brainstorm_spec, book_title_spec, blurb_spec, name_spec, golden_finger_spec, glossary_spec, opening_spec, fine_outline_spec, continue_spec, expand_spec, de_ai_spec, teardown_spec, project_plan_spec, ) } assert len(_SPECS_BY_NAME) == 23, "SPECS name 冲突或缺失" # 唯一性 + 数量自检(import 期) SPECS: Final[Mapping[str, AgentSpec]] = MappingProxyType(_SPECS_BY_NAME) # 四审受信保留名 —— 独立显式白名单(安全边界锚在此,不依附派生集合) REVIEW_RESERVED_NAMES: Final[frozenset[str]] = frozenset( {"continuity", "foreshadow", "style", "pace"} )