一本书是怎么被写出来的 书架

第 03 章 / 共 10 章

第三章 · 提示词装配

写书 agent 不是人,它读到的全部世界就是一段提示词。这一章讲 startBookJob() 怎么把这段提示词拼出来——四块料:通用前言 CODEX_BOOK_PREAMBLE、署名、登记字段(jobId/owner)、以及续跑时的补充提示。

这段代码在哪

它做什么

前言是写死的全文,一字未改过(2026-08-20 定稿至今):

const CODEX_BOOK_PREAMBLE =
  `你是跑在服务器上的自动写书代理(可跑 bash、读写文件、联网)。\n` +
  `先完整阅读 ${SKILLS_DIR}/wjs-voicedrop-writing-book/SKILL.md 并严格照做;` +
  `按它第 0 步选定书的类型后,把对应写作 skill 的 SKILL.md(同在 ${SKILLS_DIR}/ 下)也完整读进来再动笔。\n` +
  `你没有并行子代理,也没有 Workflow——skill 里说 spawn 写手/评审 subagent 的地方,一律由你自己分步串行扮演:` +
  `写完一章,抛开写作时的思路,按该类型的评审维度独立重读打分并把意见落盘 reviews/NN.json;` +
  `不过就照 must_fix 重写(最多 3 轮),过审立刻 build.mjs done 发布,绝不攒到最后。\n` +
  `其余约定(工作目录、book.json、边写边发、断点续跑、封面用 /opt/claude-agent/bin/paint)一律照 skill 执行。`;

上面的注释解释了为什么需要这段:「codex 是单代理循环,没有 Claude 的 skill 装载/并行 subagent/Workflow——skill 里这三样都要在 prompt 里翻译成『自己读文件 + 自己分步扮演角色』」。skill 文件是给人和 Claude 写的,前言负责把它「转译」成单代理能执行的版本。同一份前言也喂给 claude 腿——三条腿引擎无关,提示词完全一致。

前言之后,startBookJob() 拼上这单的专属字段:

const byline = author
  ? `作者署名「${author}」——book.json 的 author 字段用这个名字,封面/页脚照此署名。`
  : `提交者没有留名字——book.json 不写 author 字段,全书不署名(不要默认署任何人名)。`;
const prompt =
  `${CODEX_BOOK_PREAMBLE}\n\n` +
  `任务:写一本书。${byline}\n` +
  `本次写书任务号 jobId=「${jobId}」——建 book.json 时把它原样写进顶层 "jobId" 字段(登记簿要靠它对号,别漏)。\n` +
  (scope ? `这本书的产权归属 owner=「${scope}」——建 book.json 时把它原样写进顶层 "owner" 字段(谁能在线修改这本书以此为准)。\n` : "") +
  `种子:\n${seed}` +
  (resumed ? resumeAfterRestartHint(jobId) : "");

逐字段看:

注入内容形式服务端怎么用它
author署名语句,有名字/没名字两种措辞只进 book.json 的 author;没名字时明确叮嘱「不要默认署任何人名」
jobId「原样写进顶层 "jobId" 字段」收尾时 findBookByJobId() 靠它反查 slug;退款 ref=jobId 靠它幂等
owner「原样写进顶层 "owner" 字段」修书的主人校验以此为准;30 秒轮询还有兜底补写(第七章)
seed全文不进 prompt 之外的任何地方(扣费只带前 200 字)

最后是续跑提示词——系统里有两段,常被混淆:

RESUME_HINTresumeAfterRestartHint()
触发时机换腿:runBookEngine 里第 2 条腿起追加重启:inflight 孤儿续跑时追加(attempts > 1)
原因上一条腿配额/凭据倒了,书可能写了一半进程被杀,书可能「已经很完整」
措辞强度「先查有没有半成品」更具体:用 build.mjs status 对账,明确「不要重画已有的插图、不要重写已过审的章节」
带不带 jobId不带带(写书单),帮 agent 在 workspace 里认出自己的目录
function resumeAfterRestartHint(jobId?: string): string {
  return (
    `\n\n补充(本单是服务器重启后的续跑):上一次尝试被服务重启打断,很可能已经写了一部分甚至接近完成——` +
    `动笔前先列一下 ${WORKSPACE}/ 下本单的工作目录` + (jobId ? `(book.json 里 jobId=「${jobId}」的那个)` : "") +
    `,用 build.mjs status 看每章是 done / 待发(有稿) / 待写:有稿的直接发布,缺的补写,缺封面就补封面。` +
    `**不要另起一本新书、不要换 slug、不要重画已有的插图、不要重写已过审的章节。**`
  );
}

为什么这样写

怎么排查

一本书是怎么被写出来的 · 王建硕
费曼式写法 · 由多个 AI 代理撰写与互相审校