Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions assets/prompts/architect-long.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@

- **novel_context**: 获取参考模板和当前状态。优先查看 `planning_memory`、`foundation_memory`、`reference_pack` 和 `memory_policy`。`working_memory.user_rules` 是用户对本书的长期偏好(`structured` 机械约束含 chapter_words + `preferences` 自然语言偏好),规划/扩展大纲时一并遵守,与参考模板冲突时用户要求优先。
- **save_foundation**: 保存基础设定。
- **search_skills**: 本地 skill 库检索。当需要题材套路、结构模板、风格预设、流派发展史、连载套路等可复用经验时,**优先**调用本工具。返回 top-N 本地 skill 的 name/description/category/tags/priority;命中后再调 `read_skill(name=...)` 读全文。零成本、零延迟、跨书复用。
- **read_skill**: 读取本地 skill 全文(需先通过 `search_skills` 拿到 name)。
- **web_search**: 可选,联网搜索。长篇规划常涉及外部资料(如世界观考据、力量体系参考、特定流派发展史、连载套路变迁),需要时主动调用,但**仅在 search_skills 未命中后使用**。传入精炼关键词(中英文均可)。返回 `summary`(基于搜索结果的中文总结)+ `links`(参考链接列表)。若返回空结果或 `hint` 说明上游不支持,**基于已有知识继续工作,不要重试**。
- **save_materials** / **list_materials** / **remove_material**: 项目级素材库(meta/materials.json)读写工具。在规划前搜集到的命名表、术语表、视觉锚点、设定资料、参考资料等可复用素材通过 `save_materials` 批量持久化;后续 `novel_context.reference_pack.materials` 会自动注入这些素材给所有子代理消费,writer 写新场景/新角色时按需取用。

**检索优先级**:`search_skills`(本地)→ `web_search`(联网)。不要本末倒置。

## 素材收集(规划前必做)

长篇规划涉及大量细节(人名 / 地名 / 组织名 / 力量体系 / 历史年表 / 视觉锚点 等)。**调 `save_foundation` 之前**,先把这些素材沉淀到项目素材库,避免在 premise/outline 里临时编造导致后期不一致:

1. **盘点**:先调 `list_materials` 看本地已有哪些素材,避免重复搜集。
2. **搜集**:从 `search_skills`(跨书经验,可能含题材命名套路)+ `web_search`(外部考据资料)+ 自身知识。
3. **沉淀**:把搜集到的素材调一次 `save_materials` 批量写入。每条 item 含:
- `category`:`naming`(命名表)/ `terminology`(术语表)/ `visual`(视觉锚点)/ `setting`(设定资料)/ `reference`(参考资料);可自定义。
- `title`:一句话标题,便于检索("赛博朋克巨型企业命名候选")。
- `content`:素材正文,Markdown,原样注入 novel_context。命名表用列表、设定用段落、参考资料用"标题+摘要"。
- `source`:来源标记便于追溯(`web_search:query=xxx` / `skill:<name>` / `builtin`)。
4. **去重**:保存后用 `list_materials` 抽查,发现重复或不准的用 `remove_material(id=...)` 删除。

素材库是项目级的(不会跨书污染);只放**本书会用到的具体资料**——跨书经验走 `add_skill`(完结时由 coordinator 沉淀)。典型量:每本书 5-20 条素材。

## 硬约束

Expand Down
6 changes: 6 additions & 0 deletions assets/prompts/architect-short.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

- **novel_context**: 获取参考模板和当前状态。优先查看 `planning_memory`、`foundation_memory`、`reference_pack` 和 `memory_policy`,再按需读取兼容字段。`working_memory.user_rules` 是用户对本书的长期偏好(`structured` 机械约束 + `preferences` 自然语言偏好),规划时一并遵守,与参考模板冲突时用户要求优先。
- **save_foundation**: 保存基础设定
- **search_skills**: 本地 skill 库检索。当需要题材套路、结构模板、风格预设、流派常识等可复用经验时,**优先**调用本工具。返回 top-N 本地 skill 的 name/description/category/tags/priority;命中后再调 `read_skill(name=...)` 读全文。零成本、零延迟、跨书复用。
- **read_skill**: 读取本地 skill 全文(需先通过 `search_skills` 拿到 name)。
- **web_search**: 可选,联网搜索。当题材需要外部资料(如硬科幻常识、特定流派套路、历史/行业背景、时事参考)且 **search_skills 未命中**时调用,传入精炼关键词。返回 `summary`(基于搜索结果的中文总结)+ `links`(参考链接列表)。若返回空结果或 `hint` 说明上游不支持,**基于已有知识继续工作,不要重试**。
- **save_materials** / **list_materials** / **remove_material**: 项目级素材库(meta/materials.json)读写工具。短篇虽小但同样需要素材——命名 / 术语 / 视觉锚点。规划前先 `list_materials` 盘点,再 `save_materials` 把搜集到的素材批量入库。category 用 naming/terminology/visual/setting/reference;详见工具 schema。

**检索优先级**:`search_skills`(本地)→ `web_search`(联网)。不要本末倒置。

## 硬约束

Expand Down
14 changes: 14 additions & 0 deletions assets/prompts/coordinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,29 @@ architect 返回后读 `save_foundation` 的 `foundation_ready`:
- **要求重写/打磨已完成的章节** → 调 `reopen_book(chapters=[...], reason=...)` 把全书重新打开并把目标章入队,然后**等 Host 指令**——Host 会派 writer 逐章返工,全部改完后自动重新收尾完结。不要在 reopen 前先派 `subagent`,也**不需要** `save_pause_point`(返工完自动重新完结,run 自然停机就是验收点)。
- **要求续写新增剧情/扩展篇幅**(不是改旧章)→ 这超出返工范围,按上面"篇幅调整"判据处理;若确实只想在已完结的书上加章节而非重规划,告知"全书已完结,如需续写新增剧情请新建项目"。

**完结学习(可选)**:输出全书总结前,检查本次创作是否形成了可跨书复用的经验(新题材套路、解决过的结构难题、效果好的开篇模板、有效的伏笔回收方式、好用的标题节奏等)。如有,调 `add_skill(name=..., description=..., category=..., body=..., tags=[...], triggers=[...])` 把它沉淀到 `~/.ainovel/skills/` 跨书库。要求:
- 只提炼**真正可复用的**,不要把本书独有设定当通用套路;
- name 用 kebab-case 英文(如 `cyberpunk-noir-checklist`、`three-act-short`);
- description 一句话写用途;
- body 建议用 when/do/checklist 结构;
- 没有可提炼的**不要硬加**;
- 每次完结最多沉淀 2-3 条,贪多反而稀释质量。

## 工具与子代理

- `subagent(agent, task)`:调用子代理
- `novel_context`:**仅**在用户查询需要时使用;Host 指令到达后禁止先调它(指令注明"第 N 次下达"时除外)
- `search_skills`: 本地 skill 库检索。当用户查询涉及题材套路、结构模板、风格预设、流派常识等可复用经验时使用;也用于判断当前需求是否可复用过往经验。返回 name/description/category/tags/priority;命中后再调 `read_skill(name=...)` 读全文。
- `read_skill`: 读取本地 skill 全文(需先通过 `search_skills` 拿到 name)。
- `add_skill`: 仅全书完结后使用(见"### 全书完成"的完结学习说明)。把本次创作中形成的可复用经验沉淀为跨书 skill。
- `save_user_rules(text)`:把用户长效的"怎么写"风格/质量要求归一化为结构化规则并持久化(**仅**用户干预属于写作笔法/风格/质量规则时使用;剧情/结构走 architect、返工走 editor;返回的理解需回显给用户确认)
- `reopen_book(chapters, reason)`:把已完结(phase=complete)的全书重开进返工态并把目标章入队(**仅**完本后用户要求返工已写章节时使用)
- `save_pause_point(after, reason)` / `save_pause_point(cancel=true)`:登记/取消验收停靠点(**仅**写作期用户要求改已写章节且未表达继续写意图时,在派 editor 前登记;重写队列排空后系统自动暂停等验收)
- `web_search(query)`:可选,联网搜索外部资料(题材常识、流派套路、参考资料等)。**仅**在调度判断确实需要外部知识补足且 `search_skills` 未命中时使用;写作期不主动调(writer 自己不挂这工具)。返回 `summary` + `links`;返回空或 `hint` 时基于已有信息继续,不要重试。
- 子代理:`architect_long` / `architect_short` / `writer` / `editor`

**检索优先级**:`search_skills`(本地)→ `web_search`(联网)。本地 skill 零成本、跨书复用,应优先尝试。

## 禁止

- 在 Host 指令到达时先调 novel_context 或输出推理再行动
Expand Down
12 changes: 9 additions & 3 deletions cmd/ainovel-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/voocel/ainovel-cli/assets"
"github.com/voocel/ainovel-cli/internal/bootstrap"
"github.com/voocel/ainovel-cli/internal/cli"
"github.com/voocel/ainovel-cli/internal/entry/headless"
"github.com/voocel/ainovel-cli/internal/entry/tui"
"github.com/voocel/ainovel-cli/internal/eval"
Expand All @@ -25,9 +26,14 @@ var (
var headlessMode bool

func main() {
// 子命令在常规 flag 解析之前拦截:eval 是离线评测 harness,参数体系独立。
if len(os.Args) > 1 && os.Args[1] == "eval" {
os.Exit(eval.Command(os.Args[2:]))
// 子命令在常规 flag 解析之前拦截:eval / skill 参数体系独立,不需要加载配置。
if len(os.Args) > 1 {
switch os.Args[1] {
case "eval":
os.Exit(eval.Command(os.Args[2:]))
case "skill":
os.Exit(cli.SkillCommand(os.Args[2:]))
}
}

opts, args, err := parseCLIOptions(os.Args[1:])
Expand Down
58 changes: 55 additions & 3 deletions internal/agents/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"log/slog"
"path/filepath"
"regexp"
"strconv"
"strings"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/voocel/ainovel-cli/internal/domain"
"github.com/voocel/ainovel-cli/internal/host/reminder"
"github.com/voocel/ainovel-cli/internal/rules"
"github.com/voocel/ainovel-cli/internal/skills"
"github.com/voocel/ainovel-cli/internal/store"
"github.com/voocel/ainovel-cli/internal/tools"
"github.com/voocel/ainovel-cli/internal/userrules"
Expand Down Expand Up @@ -121,9 +123,25 @@ func BuildCoordinator(
recordUsage UsageRecorder,
onFlowBoundary FlowBoundaryHook,
onGuardBlock reminder.BlockHook,
) (*agentcore.Agent, *tools.AskUserTool, *ctxpack.WriterRestorePack, *corecontext.ContextEngine, ApplyThinking) {
) (*agentcore.Agent, *tools.AskUserTool, *ctxpack.WriterRestorePack, *corecontext.ContextEngine, ApplyThinking, *tools.WebSearchTool, *skills.Store) {
// 共享工具
contextTool := tools.NewContextTool(store, bundle.References, cfg.Style)

// 跨书 skill 库。失败时不阻断主流程:skill 工具返回空结果,走 web_search fallback。
skillStore := skills.NewStore(filepath.Join(bootstrap.DefaultConfigDir(), "skills"))
if err := skillStore.Refresh(); err != nil {
slog.Warn("skill 库初始化失败,本次禁用", "module", "agent", "err", err)
skillStore = nil
}

// 联网搜索工具:复用主 provider 链路(代理→上游 provider,触发 Anthropic 服务端
// web_search_20250305)。零额外配置,baseURL/apiKey/model 都从顶层推导。
// 仅挂载到 architect 与 coordinator:写作阶段(writer/editor)需要的是
// 故事内一致性而非外部资料,挂上去反而引入幻觉风险。
var webSearchTool *tools.WebSearchTool
if pc, ok := cfg.Providers[cfg.Provider]; ok && pc.BaseURL != "" {
webSearchTool = tools.NewWebSearchTool(pc.BaseURL, pc.APIKey, cfg.ModelName)
}
// 用户规则服务:归一化各来源 → 确定性合并 → 落盘本书快照。Coordinator 的
// save_user_rules 工具复用它做运行中更新;归一化用 Default 模型(与 Host 开书侧一致)。
userRulesSvc := userrules.NewService(store, models.Default, rules.DefaultOptions())
Expand All @@ -133,6 +151,21 @@ func BuildCoordinator(
architectTools := []agentcore.Tool{
contextTool,
tools.NewSaveFoundationTool(store),
// 素材库写工具:architect 收集到的命名/术语/视觉/设定素材落盘到 meta/materials.json,
// 后续 novel_context.reference_pack.materials 自动注入。
tools.NewSaveMaterialsTool(store),
tools.NewListMaterialsTool(store),
tools.NewRemoveMaterialTool(store),
}
if webSearchTool != nil {
architectTools = append(architectTools, webSearchTool)
}
// 本地 skill 优先于联网:architect 规划时先看跨书经验,未命中再查外部。
if skillStore != nil {
architectTools = append(architectTools,
tools.NewSkillSearchTool(skillStore),
tools.NewSkillReadTool(skillStore),
)
}
writerTools := []agentcore.Tool{
contextTool,
Expand Down Expand Up @@ -341,10 +374,29 @@ func BuildCoordinator(
CommitOnProject: true,
})

coordinatorTools := []agentcore.Tool{
subagentTool,
contextTool,
tools.NewSaveUserRulesTool(userRulesSvc),
tools.NewReopenBookTool(store),
tools.NewSavePausePointTool(store),
}
if webSearchTool != nil {
coordinatorTools = append(coordinatorTools, webSearchTool)
}
// coordinator 也挂 skill 读工具,用于"用户查询"类场景;写工具 add_skill 仅限完结学习。
if skillStore != nil {
coordinatorTools = append(coordinatorTools,
tools.NewSkillSearchTool(skillStore),
tools.NewSkillReadTool(skillStore),
tools.NewSkillAddTool(skillStore),
)
}

agent := agentcore.NewAgent(
agentcore.WithModel(coordinatorModel),
agentcore.WithSystemPrompt(bundle.Prompts.Coordinator),
agentcore.WithTools(subagentTool, contextTool, tools.NewSaveUserRulesTool(userRulesSvc), tools.NewReopenBookTool(store), tools.NewSavePausePointTool(store)),
agentcore.WithTools(coordinatorTools...),
agentcore.WithMaxTurns(100_000),
agentcore.WithOnMessage(coordinatorOnMessage),
agentcore.WithToolsAreIdempotent(true),
Expand Down Expand Up @@ -384,7 +436,7 @@ func BuildCoordinator(
}
}

return agent, askUser, restore, coordinatorEngine, applyThinking
return agent, askUser, restore, coordinatorEngine, applyThinking, webSearchTool, skillStore
}

func flowBoundaryMiddleware(onBoundary FlowBoundaryHook) agentcore.ToolMiddleware {
Expand Down
2 changes: 1 addition & 1 deletion internal/bootstrap/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/voocel/ainovel-cli/internal/errs"
)

// 长输出 + 长 ctx 场景下,reasoning-aware provider(mimo / deepseek-r1 等
// 长输出 + 长 ctx 场景下,reasoning-aware provider(如某些支持 reasoning 的模型
// 思考阶段如果 server 端不流式发 reasoning delta,SSE 整段会保持沉默。
// litellm 默认 watchdog 是 2 分钟,对 8000 字写作章节经常触发误杀。
// 5 分钟覆盖绝大多数实测案例(参见 tasks/todo.md plan→draft 思考时长统计),
Expand Down
Loading