refactor(core): drop revise_chapter agent tool, route through sub_agent reviser

The revise_chapter tool and sub_agent(agent="reviser") both wrapped the
same pipeline.reviseDraft() call with an almost-identical parameter set.
Listing both in the system prompt made the model oscillate between them.

Removes createReviseChapterTool, its export, registration, and tests.
Updates the system prompt and edit/write tool descriptions to point at
sub_agent(reviser) for whole-chapter rewrites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
fanghanjun 2026-04-16 10:52:36 -07:00
parent 52250539db
commit a57f9db0d5
6 changed files with 35 additions and 73 deletions

View file

@ -55,7 +55,6 @@ describe("buildAgentSystemPrompt", () => {
expect(prompt).toContain("mode");
expect(prompt).toContain("approvedOnly");
expect(prompt).toContain("read");
expect(prompt).toContain("revise_chapter");
expect(prompt).toContain("write_truth_file");
expect(prompt).toContain("rename_entity");
expect(prompt).toContain("patch_chapter_text");
@ -65,12 +64,22 @@ describe("buildAgentSystemPrompt", () => {
expect(prompt).toContain("ls");
});
it("with-book prompt steers high-risk edits to dedicated deterministic tools", () => {
it("Chinese prompt does NOT mention revise_chapter (merged into sub_agent reviser)", () => {
const prompt = buildAgentSystemPrompt("my-book", "zh");
expect(prompt).not.toContain("revise_chapter");
});
it("English prompt does NOT mention revise_chapter (merged into sub_agent reviser)", () => {
const prompt = buildAgentSystemPrompt("novel", "en");
expect(prompt).not.toContain("revise_chapter");
});
it("with-book prompt steers chapter rewrites to sub_agent reviser, not a separate tool", () => {
const prompt = buildAgentSystemPrompt("my-book", "zh");
expect(prompt).toContain("改设定/改真相文件");
expect(prompt).toContain("write_truth_file");
expect(prompt).toContain("用户要求重写/精修已有章节");
expect(prompt).toContain("revise_chapter");
expect(prompt).toContain("reviser");
expect(prompt).toContain("也可以直接使用 edit / write");
});

View file

@ -7,7 +7,6 @@ import {
createSubAgentTool,
createPatchChapterTextTool,
createRenameEntityTool,
createReviseChapterTool,
createWriteFileTool,
createWriteTruthFileTool,
} from "../agent/agent-tools.js";
@ -65,27 +64,6 @@ describe("agent deterministic writing tools", () => {
.resolves.toContain("distrusts the guild");
});
it("routes chapter rewrite requests through reviseDraft with the requested mode", async () => {
const pipeline = {
reviseDraft: vi.fn(async () => ({
chapterNumber: 3,
wordCount: 1300,
fixedIssues: [],
applied: true,
status: "ready-for-review" as const,
})),
};
const tool = createReviseChapterTool(pipeline as never, "harbor");
const result = await tool.execute("tool-2", {
chapterNumber: 3,
mode: "rewrite",
});
expect(pipeline.reviseDraft).toHaveBeenCalledWith("harbor", 3, "rewrite");
expect(result.content[0]?.type).toBe("text");
});
it("renames entities through the deterministic edit controller", async () => {
const tool = createRenameEntityTool({} as never, root, "harbor");

View file

@ -8,7 +8,6 @@ import {
createPatchChapterTextTool,
createRenameEntityTool,
createSubAgentTool,
createReviseChapterTool,
createReadTool,
createEditTool,
createWriteFileTool,
@ -237,7 +236,6 @@ export async function runAgentSession(
tools: [
createSubAgentTool(pipeline, bookId, projectRoot),
createReadTool(projectRoot),
createReviseChapterTool(pipeline, bookId),
createWriteTruthFileTool(pipeline, projectRoot, bookId),
createRenameEntityTool(pipeline, projectRoot, bookId),
createPatchChapterTextTool(pipeline, projectRoot, bookId),

View file

@ -73,17 +73,20 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
##
- **sub_agent**
- agent="writer" chapterWordCount
- agent="auditor" chapterNumber
- agent="reviser" chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect
- agent="writer" ****chapterWordCount
- agent="auditor" ****chapterNumber
- agent="reviser" ****** chapterNumber **chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect
- agent="exporter" format: txt/md/epub, approvedOnly: true/false
- **writer vs reviser **
- "改/修订/重写第 N 章""第 N 章 xxx 写得不好" **reviser** + chapterNumber=N writerwriter N+1
- "写下一章""继续写""再来一章" **writer** reviser chapterNumber reviser
- "改一下刚才那章" **reviser** + chapterNumber=
- **read**
- **revise_chapter** //
- **write_truth_file** story_biblevolume_outlinebook_rulescurrent_focus
- **rename_entity** /
- **patch_chapter_text**
- **edit** patch_chapter_text
- **write** write_truth_file revise_chapter
- **write** write_truth_file/ sub_agent reviser
- **grep** "哪一章提到了某个角色"
- **ls**
@ -92,7 +95,7 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
- 使 sub_agent
- read
- / write_truth_file
- / revise_chapter
- / sub_agent(agent="reviser", chapterNumber=N, mode=...)
- rename_entity
- patch_chapter_text
- 使 edit / write
@ -122,17 +125,20 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
## Available Tools
- **sub_agent** Delegate to sub-agents:
- agent="writer" write next chapter (params: chapterWordCount for word count override)
- agent="auditor" audit chapter quality (params: chapterNumber to target specific chapter)
- agent="reviser" revise chapter (params: chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect)
- agent="writer" **continue writing the NEXT chapter** (always appends after the latest written chapter; cannot target a specific number. params: chapterWordCount)
- agent="auditor" audit an **EXISTING chapter** (params: chapterNumber to target a specific chapter; omit for the latest)
- agent="reviser" modify an **EXISTING chapter** (**chapterNumber is required to identify which chapter**. params: chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect)
- agent="exporter" export book (params: format: txt/md/epub, approvedOnly: true/false)
- **writer vs reviser common mistake, read carefully**:
- User says "revise/rewrite/fix chapter N" or "chapter N has issues" **reviser** with chapterNumber=N (never writer writer would produce a new chapter N+1)
- User says "write the next chapter" / "continue" / "one more chapter" **writer** (never reviser, and never call reviser without chapterNumber)
- User refers to "that chapter we just did" without a number **reviser** with chapterNumber=latest-written
- **read** Read truth files or chapter content
- **revise_chapter** Rewrite or polish an existing chapter
- **write_truth_file** Replace a canonical truth file in story/
- **rename_entity** Rename a character or entity across the book
- **patch_chapter_text** Apply a local deterministic patch to a chapter
- **edit** Exact string replacement on setting files (use patch_chapter_text for chapter text)
- **write** Create a new file, or fully replace an existing file's content (prefer write_truth_file for canonical truth files, revise_chapter for chapter revisions)
- **write** Create a new file, or fully replace an existing file's content (prefer write_truth_file for canonical truth files; for whole-chapter rewrites call sub_agent with agent="reviser")
- **grep** Search content across chapters
- **ls** List files or chapters
@ -141,7 +147,7 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
- Use sub_agent for heavy operations (writing, revision, auditing)
- Use read first for settings inquiries
- Use write_truth_file for truth files and setting changes
- Use revise_chapter for rewrite/polish/rework of existing chapters
- For rewrite/polish/rework of an existing chapter sub_agent(agent="reviser", chapterNumber=N, mode=...)
- Use rename_entity for character/entity renames
- Use patch_chapter_text for local chapter fixes
- Use edit / write directly when you already know the exact target file and replacement content

View file

@ -1,7 +1,7 @@
import { Type, type Static } from "@mariozechner/pi-ai";
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@mariozechner/pi-agent-core";
import type { PipelineRunner } from "../pipeline/runner.js";
import { DEFAULT_REVISE_MODE, type ReviseMode } from "../agents/reviser.js";
import { type ReviseMode } from "../agents/reviser.js";
import { readFile, writeFile, readdir, stat } from "node:fs/promises";
import { join, normalize, resolve } from "node:path";
import { StateManager } from "../state/manager.js";
@ -226,36 +226,6 @@ export function createSubAgentTool(
// 2. Deterministic writing tools
// ---------------------------------------------------------------------------
const ReviseChapterParams = Type.Object({
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
chapterNumber: Type.Number({ description: "Chapter number to revise." }),
mode: Type.Optional(Type.Union([
Type.Literal("spot-fix"),
Type.Literal("polish"),
Type.Literal("rewrite"),
Type.Literal("rework"),
Type.Literal("anti-detect"),
])),
});
export function createReviseChapterTool(
pipeline: PipelineRunner,
activeBookId: string | null,
): AgentTool<typeof ReviseChapterParams> {
return {
name: "revise_chapter",
description: "Revise a specific chapter through the deterministic revision pipeline.",
label: "Revise Chapter",
parameters: ReviseChapterParams,
async execute(_toolCallId, params): Promise<AgentToolResult<undefined>> {
const bookId = resolveToolBookId("revise_chapter", params.bookId, activeBookId);
const mode = params.mode ?? DEFAULT_REVISE_MODE;
await pipeline.reviseDraft(bookId, params.chapterNumber, mode);
return textResult(`Revision (${mode}) complete for "${bookId}" chapter ${params.chapterNumber}.`);
},
};
}
const WriteTruthFileParams = Type.Object({
bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
fileName: Type.String({ description: "Truth file name under story/, e.g. story_bible.md or current_focus.md." }),
@ -395,7 +365,8 @@ export function createEditTool(projectRoot: string): AgentTool<typeof EditParams
description:
"Edit a file under books/ via exact string replacement. " +
"old_string must appear exactly once in the file. " +
"For chapter text use patch_chapter_text; for canonical truth files (story_bible/volume_outline/book_rules/current_focus) prefer write_truth_file.",
"For chapter text use patch_chapter_text; for canonical truth files (story_bible/volume_outline/book_rules/current_focus) prefer write_truth_file; " +
"to rewrite or polish a whole chapter call sub_agent with agent=\"reviser\".",
label: "Edit File",
parameters: EditParams,
async execute(
@ -439,7 +410,8 @@ export function createWriteFileTool(projectRoot: string): AgentTool<typeof Write
description:
"Create a new file, or fully replace an existing file's content under books/. " +
"Parent directories are created automatically. Existing content is overwritten silently — " +
"for canonical truth files prefer write_truth_file; for chapter revisions use revise_chapter.",
"for canonical truth files prefer write_truth_file; " +
"for whole-chapter rewrites/polishing call sub_agent with agent=\"reviser\".",
label: "Write File",
parameters: WriteFileParams,
async execute(

View file

@ -2,7 +2,6 @@ export { buildAgentSystemPrompt } from "./agent-system-prompt.js";
export {
createSubAgentTool,
createReadTool,
createReviseChapterTool,
createWriteTruthFileTool,
createRenameEntityTool,
createPatchChapterTextTool,