Want to keep using Fable 5? Teach Opus and Sonnet to "behave" like it.

Want to keep using Fable 5? Teach Opus and Sonnet to “behave” like it.

想要继续使用 Fable 5?教 Opus 和 Sonnet 像它一样“行事”。

A while back I wrote about how I use Fable 5 without going token-bankrupt. I put Fable 5 in the orchestrator seat — scoping, design, delegation, final review — while the hands-on implementation, testing, shipping, and code review are done by Opus and Sonnet subagents. The expensive model plans and judges; the cheaper models execute. This ran beautifully for most tasks. 前段时间我写过关于如何在不耗尽 Token 的情况下使用 Fable 5 的文章。我让 Fable 5 担任协调员的角色——负责范围界定、设计、任务委派和最终审查——而实际的编码实现、测试、发布和代码审查则由 Opus 和 Sonnet 子代理完成。昂贵的模型负责规划和判断,便宜的模型负责执行。这套流程在大多数任务中运行得非常完美。

There’s just one problem. As you probably know, Fable 5 access via subscription ends on July 7. (I’m praying it comes back to subscription someday… 🙏) The orchestration setup itself works without Fable 5 — but if the model at the top, the one cutting scope and judging everything, gets downgraded, output quality drops with it. 但有一个问题。如你所知,通过订阅访问 Fable 5 的服务将于 7 月 7 日结束。(我祈祷它未来某天能回归订阅制……🙏)这种协调架构本身脱离 Fable 5 也能运行,但如果处于顶层、负责界定范围和进行判断的模型被降级,输出质量也会随之下降。

Here’s the thing though: Opus and Sonnet are not bad models. Used correctly, they produce genuinely good output. What matters is writing down what to do, how to do it, and what to do when something fails. So before losing access, I decided to document how Fable 5 actually works and hand it down to Opus/Sonnet — as skills. This post is about what I did and how. 但关键在于:Opus 和 Sonnet 并不是糟糕的模型。如果使用得当,它们能产出非常优质的结果。重要的是要写清楚该做什么、怎么做,以及在失败时该怎么办。因此,在失去访问权限之前,我决定将 Fable 5 的工作方式记录下来,并将其作为“技能”传授给 Opus/Sonnet。这篇文章就是关于我做了什么以及如何做的。

Finding a “replacement for Fable” seemed hopeless, so the goal became something else: raise the whole team’s capability so that quality holds even with Opus in the orchestrator seat. 寻找“Fable 的替代品”似乎毫无希望,因此目标变成了另一个:提升整个团队的能力,即使由 Opus 担任协调员,也能保持高质量的输出。

The gap between Fable 5 and Opus/Sonnet is not intelligence

Fable 5 与 Opus/Sonnet 之间的差距不在于智能

To keep using a pseudo-Fable after the shutdown, I started by examining what the difference actually is. When Opus/Sonnet produce output I didn’t intend during everyday engineering, the failures turn out to follow recognizable patterns. For example: 为了在停用后继续使用“伪 Fable”,我首先分析了它们之间的实际差异。在日常工程中,当 Opus/Sonnet 产出非预期的结果时,这些失败往往遵循可识别的模式。例如:

  • Slapping “use client” at the top of a Next.js page because one button needs an onClick: …which drops the entire page and its import tree onto the client. 因为一个按钮需要 onClick,就在 Next.js 页面顶部加上 “use client”: ……这会导致整个页面及其导入树都被推送到客户端。
  • Writing fetch with no cache option: The defaults are opposite between Next.js 14 and 15, but “it works” was only checked on the dev server — hello, production caching surprise. 编写 fetch 时不加缓存选项: Next.js 14 和 15 之间的默认设置相反,但如果只在开发服务器上检查“它能运行”,那么恭喜你,生产环境的缓存惊喜在等着你。
  • Skipping input validation in a Server Action because it’s “only called from my form”: It’s actually a public POST endpoint anyone can hit with arbitrary arguments. 在 Server Action 中跳过输入验证,因为“它只会被我的表单调用”: 实际上,这是一个任何人都可以用任意参数访问的公共 POST 端点。
  • Fetching initial data inside useEffect: An await in a Server Component would do it, without the request waterfall and the layout flash. 在 useEffect 中获取初始数据: 在 Server Component 中使用 await 就能搞定,而且不会产生请求瀑布流和布局闪烁。
  • “Fixing” a failing test by rewriting the assertion: Don’t touch the assertion until you’ve proven, with evidence, that the code side is the wrong one. 通过重写断言来“修复”失败的测试: 在你用证据证明代码端确实有问题之前,不要动断言。
  • Saying “this should work now” without running anything: Run it first, then conclude whether it works. 什么都不运行就说“现在应该能行了”: 先运行,再下结论。

What matters here: none of these are knowledge gaps. Ask Sonnet directly — “is a Next.js Server Action dangerous without input validation?” — and it answers correctly. The knowledge is in there. What’s missing is the correct procedure. Fable 5 runs these checks implicitly, without being asked, and pushes back when something’s off. With Opus/Sonnet, those checks have to be written down explicitly. 重点在于:这些都不是知识缺口。直接问 Sonnet——“没有输入验证的 Next.js Server Action 是否危险?”——它能回答正确。知识就在那里,缺失的是正确的流程。Fable 5 会在不被要求的情况下隐式执行这些检查,并在发现问题时提出异议。而对于 Opus/Sonnet,这些检查必须被显式地写下来。

Raw reasoning differences are real, of course. But for routine implementation, review, and debugging, most of the observable quality gap comes down to discipline, sequencing, and verification — and all three can be written as skills. 当然,原始推理能力的差异确实存在。但对于日常的实现、审查和调试,大部分可观察到的质量差距归结于纪律、顺序和验证——而这三者都可以被编写为技能。

“Be careful” prompts don’t work

“小心点”之类的提示词没用

My first attempt at Fable-izing Opus/Sonnet was writing “be careful, always verify, follow best practices” into the prompt. Result: nothing happened (obviously, in hindsight). A sentence that doesn’t change the model’s next move doesn’t change its behavior. 我第一次尝试将 Opus/Sonnet “Fable 化”时,是在提示词中写下“要小心,始终进行验证,遵循最佳实践”。结果:什么都没发生(事后看来显而易见)。一句不能改变模型下一步动作的话,是无法改变其行为的。

So the first rule of documenting Fable’s working method was a ban list. No line may contain: “be careful” / “follow best practices” / “think hard” / “ensure quality”. Every line has to be executable or checkable — runnable as a command, defined as a threshold, expressed as if/then, or greppable as a pattern. It must be unambiguous what the reader does next. 因此,记录 Fable 工作方法的第一条规则是建立一个“禁用列表”。任何一行都不得包含:“要小心” / “遵循最佳实践” / “深入思考” / “确保质量”。每一行都必须是可执行或可检查的——可以作为命令运行、定义为阈值、用 if/then 表达,或者作为模式进行 grep 搜索。读者下一步该做什么必须是明确无误的。

I documented Fable 5’s working method along five axes

我从五个维度记录了 Fable 5 的工作方法

Before getting into the “how”: I maintain ccteams, a CLI that installs pre-built Claude Code subagent teams with one command. From v0.2.x, every team ships with two skills: a shared working method and a per-team playbook. Here’s what actually went into them, in order. 在进入“如何做”之前:我维护着 ccteams,这是一个可以通过一条命令安装预构建 Claude Code 子代理团队的 CLI 工具。从 v0.2.x 版本开始,每个团队都附带两项技能:共享工作方法和特定团队的操作手册。以下是它们实际包含的内容,按顺序排列:

1. Fix the work routine

1. 规范工作流程

The classic Opus/Sonnet mistake is starting to write immediately — leaning on training data or earlier context (“Go is usually written like this”, “this library’s API was like that”) without checking the repository in front of it. So each playbook opens with a numbered procedure that forces pre-writing behavior: Opus/Sonnet 的典型错误是立即开始编写代码——依赖训练数据或之前的上下文(“Go 通常是这样写的”、“这个库的 API 是那样的”),而不检查眼前的代码库。因此,每本操作手册的开头都有一套编号流程,强制执行编写前的行为:

  • Read go.mod (and its version) before using a language feature. 在使用语言特性前,先读取 go.mod(及其版本)。
  • Read the lockfile for the actually-installed version before trusting library docs. 在信任库文档之前,先读取 lockfile 以确认实际安装的版本。
  • Read two neighboring files before picking a pattern. 在选择模式之前,先阅读两个相邻的文件。
  • grep before writing a helper (it usually already exists under a different name). 在编写辅助函数前先 grep(它通常已经以不同的名称存在了)。

In short, it pre-commits the model to one principle: don’t decide from memory — read the repo and decide. 简而言之,它让模型预先承诺一个原则:不要凭记忆做决定——阅读代码库后再做决定。

2. Define the failure patterns and their fixes

2. 定义失败模式及其修复方法

What mid-tier models lack isn’t knowledge — it’s that they carry wrong assumptions, and proceed on the premise that the plausible implementation is the correct one. Crushing that tendency is the priority. So for each team, I defined the common failure patterns in a fixed format: symptom → wrong instinct → correct move. 中端模型缺乏的不是知识,而是它们带有错误的假设,并基于“看似合理的实现就是正确的”这一前提进行操作。压制这种倾向是首要任务。因此,对于每个团队,我以固定的格式定义了常见的失败模式:症状 → 错误的直觉 → 正确的操作

From the ccteams Next.js playbook: 以 ccteams 的 Next.js 操作手册为例:

  • Symptom: One button needs an onClick.
  • Wrong instinct: Put “use client” at the top of the page.
  • Correct move: Extract the button into its own leaf component, mark only that file client, keep the page a Server Component.
    • 症状: 一个按钮需要 onClick。
    • 错误的直觉: 在页面顶部加上 “use client”。
    • 正确的操作: 将按钮提取为独立的叶子组件,仅在该文件中标记为 client,保持页面为 Server Component。

From the Go playbook: 以 Go 操作手册为例:

  • Symptom: Handler hits an error.
  • Wrong instinct: Call http.Error(w, msg, 500) and keep going.
  • Correct move: Add return immediately after. http.Error writes and returns; your handler doesn’t — the success path below writes a second body onto a committed 500 response.
    • 症状: 处理程序遇到错误。
    • 错误的直觉: 调用 http.Error(w, msg, 500) 然后继续执行。
    • 正确的操作: 紧接着添加 returnhttp.Error 会写入并返回;但你的处理程序不会——下方的成功路径会在已提交的 500 响应上写入第二个响应体。

There are 10–15 of these per team (Go team, React Native team, Rails team, and so on). Naming the wrong instinct explicitly… 每个团队(Go 团队、React Native 团队、Rails 团队等)都有 10-15 个这样的条目。明确指出错误的直觉……