feat(model-overlays): add fable-5, opus-4-8, and sonnet-5 overlays + resolver mappings

model-overlays/ had no entry for the current Claude generation, so every
session on a Claude 5 family or Opus 4.8 model fell through to the generic
claude.md nudges. Adds the three overlays with resolver mappings and
per-overlay tests; generated output for the default host is unchanged
(overlays activate by detected model).

Closes #2509.

Contributed by @chrisquorum (PRs #2246, #2243, #2247).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-14 19:14:06 -07:00
parent d7ce124092
commit 81659f9456
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
7 changed files with 272 additions and 0 deletions

23
model-overlays/fable-5.md Normal file
View File

@ -0,0 +1,23 @@
{{INHERIT:claude}}
**Act when you have enough to act.** Fable 5 can over-plan on ambiguous tasks.
When you have enough information to act, act. Do not re-derive facts already
established in the conversation, re-litigate a decision the user has already made,
or narrate options you will not pursue in user-facing messages. Give a
recommendation, not an exhaustive survey. This does not apply to thinking blocks.
**Ground progress claims in evidence.** Before reporting progress, audit each
claim against a tool result from this session. Report only work you can point to;
if something is not yet verified, say so. If tests fail, say so with the output;
if a step was skipped, say that; when something is done and verified, state it
plainly without hedging.
**Assessment vs action.** When the user is describing a problem, asking a
question, or thinking out loud rather than requesting a change, the deliverable is
your assessment: report findings and stop. Don't apply a fix until they ask. Before
a state-changing command (restart, delete, config edit), confirm the evidence
supports that specific action.
**Delegate independent work.** When a task fans out across independent items,
delegate to sub-agents and keep working while they run, rather than iterating
serially. Intervene if a sub-agent goes off track or is missing context.

View File

@ -0,0 +1,23 @@
{{INHERIT:claude}}
**Effort-match the step.** Simple file reads, config checks, command lookups, and
mechanical edits don't need deep reasoning. Complete them quickly and move on. Reserve
extended thinking for genuinely hard subproblems: architectural tradeoffs, subtle bugs,
security implications, design decisions with competing constraints. Over-thinking
simple steps wastes tokens and time.
**Pace questions to the skill.** If the current skill's text contains
`STOP. AskUserQuestion` anywhere, pace one question per turn — emit the question as
a tool_use, stop, wait for the user's response, then continue. Do not batch. A
finding with an "obvious fix" is still a finding and still needs user approval
before it lands in the plan. Only batch clarifying questions upfront when (a) the
skill has no `STOP. AskUserQuestion` directive AND (b) you need multiple unrelated
clarifications before you can begin. When in doubt, ask one question per turn.
**Literal interpretation awareness.** Opus 4.8 interprets instructions literally and
will not silently generalize. When the user says "fix the tests," fix all failing tests
that this branch introduced or is responsible for, not just the first one (and not
pre-existing failures in unrelated code). When the user says "update the docs," update
every relevant doc in scope, not just the most obvious one. Read the full scope of what
was asked and deliver the full scope. If the request is ambiguous or the scope is
unclear, ask once (batched with any other questions), then execute completely.

View File

@ -0,0 +1,17 @@
{{INHERIT:claude}}
**Instructions are read literally.** Sonnet 5 does not silently generalize an
instruction from one item to the next, and it does not infer requests you didn't
make. When something should apply broadly, say so ("apply this to every section,
not just the first"). Re-baseline holdover style directives — they now land at
face value.
**Scope work to the request.** At lower effort especially, Sonnet 5 scopes to
exactly what was asked rather than going above and beyond. If reasoning looks
shallow on a genuinely complex task, that is an effort signal: raise effort rather
than adding prose guardrails.
**Verbosity tracks task complexity.** Responses calibrate length to how complex
the task looks — shorter on lookups, longer on open-ended analysis. If you need a
specific length or format, state it; a positive example of the target beats a
"don't be verbose" instruction.

View File

@ -14,6 +14,9 @@
export const ALL_MODEL_NAMES = [
'claude',
'opus-4-7',
'fable-5',
'opus-4-8',
'sonnet-5',
'gpt',
'gpt-5.4',
'gemini',
@ -53,6 +56,9 @@ export function resolveModel(input: string): Model | null {
if (/^gpt(-|$)/.test(s)) return 'gpt';
if (/^o[0-9]+(-|$)/.test(s)) return 'o-series';
if (/^claude-opus-4-7(-|$)/.test(s)) return 'opus-4-7';
if (/^claude-fable-5(-|$)/.test(s)) return 'fable-5';
if (/^claude-opus-4-8(-|$)/.test(s)) return 'opus-4-8';
if (/^claude-sonnet-5(-|$)/.test(s)) return 'sonnet-5';
if (/^claude(-|$)/.test(s)) return 'claude';
if (/^gemini(-|$)/.test(s)) return 'gemini';

View File

@ -0,0 +1,55 @@
/**
* Fable 5 model overlay gate-tier assertions on the family nudges.
*
* fable-5 inherits the claude base and adds Fable-family nudges: act when you
* have enough context (avoid over-planning), ground progress claims in tool
* results, assessment-vs-action boundaries, and delegate independent work.
*/
import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import type { TemplateContext } from '../scripts/resolvers/types';
import { HOST_PATHS } from '../scripts/resolvers/types';
import { generateModelOverlay } from '../scripts/resolvers/model-overlay';
function makeCtx(model: string): TemplateContext {
return {
skillName: 'test-skill',
tmplPath: 'test.tmpl',
host: 'claude',
paths: HOST_PATHS.claude,
preambleTier: 2,
model,
};
}
const ROOT = path.resolve(__dirname, '..');
describe('Fable 5 overlay — family nudges', () => {
test('raw fable-5.md contains the act-when-ready nudge', () => {
const raw = fs.readFileSync(path.join(ROOT, 'model-overlays/fable-5.md'), 'utf-8');
expect(raw).toContain('Act when you have enough to act');
});
test('resolved overlay inherits from claude base (INHERIT:claude)', () => {
const out = generateModelOverlay(makeCtx('fable-5'));
expect(out).toContain('Todo-list discipline');
expect(out).toContain('subordinate');
});
test('resolved overlay carries the Fable nudges', () => {
const out = generateModelOverlay(makeCtx('fable-5'));
expect(out).toContain('Act when you have enough to act');
expect(out).toContain('Ground progress claims in evidence');
});
test('resolved overlay has no unresolved INHERIT directive', () => {
const out = generateModelOverlay(makeCtx('fable-5'));
expect(out).not.toContain('{{INHERIT:');
});
test('claude overlay (base) does not carry the Fable nudge', () => {
const out = generateModelOverlay(makeCtx('claude'));
expect(out).not.toContain('Act when you have enough to act');
});
});

View File

@ -0,0 +1,92 @@
/**
* Opus 4.8 model overlay gate-tier assertions on the pacing directive.
*
* opus-4-8 mirrors opus-4-7's Opus-4.x family nudges: it inherits the claude
* base and adds effort-matching, skill-paced questions (one-per-turn when the
* skill carries STOP directives), and complete-scope literal execution.
*
* This test asserts:
* - The "Pace questions to the skill" directive is present
* - The old "Batch your questions" directive is absent
* - The AUTO_DECIDE-compatible language survives (subordination, skill wins)
* - The claude base is inherited (INHERIT:claude)
*/
import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import type { TemplateContext } from '../scripts/resolvers/types';
import { HOST_PATHS } from '../scripts/resolvers/types';
import { generateModelOverlay } from '../scripts/resolvers/model-overlay';
function makeCtx(model: string): TemplateContext {
return {
skillName: 'test-skill',
tmplPath: 'test.tmpl',
host: 'claude',
paths: HOST_PATHS.claude,
preambleTier: 2,
model,
};
}
const ROOT = path.resolve(__dirname, '..');
describe('Opus 4.8 overlay — pacing directive', () => {
test('raw opus-4-8.md contains "Pace questions to the skill"', () => {
const raw = fs.readFileSync(
path.join(ROOT, 'model-overlays/opus-4-8.md'),
'utf-8',
);
expect(raw).toContain('Pace questions to the skill');
});
test('raw opus-4-8.md does NOT contain "Batch your questions" directive', () => {
const raw = fs.readFileSync(
path.join(ROOT, 'model-overlays/opus-4-8.md'),
'utf-8',
);
expect(raw).not.toContain('**Batch your questions.**');
});
test('resolved overlay output contains "Pace questions to the skill"', () => {
const out = generateModelOverlay(makeCtx('opus-4-8'));
expect(out).toContain('Pace questions to the skill');
});
test('resolved overlay inherits from claude base (INHERIT:claude)', () => {
const out = generateModelOverlay(makeCtx('opus-4-8'));
// The claude base contributes the subordination wrapper + Todo discipline
expect(out).toContain('Todo-list discipline');
expect(out).toContain('subordinate');
});
test('resolved overlay says skill STOP directives trigger one-per-turn pacing', () => {
const out = generateModelOverlay(makeCtx('opus-4-8'));
expect(out).toMatch(/STOP\. AskUserQuestion/);
expect(out).toMatch(/pace one question per turn|one question per turn/i);
});
test('resolved overlay requires AskUserQuestion as tool_use', () => {
const out = generateModelOverlay(makeCtx('opus-4-8'));
expect(out).toContain('tool_use');
});
test('resolved overlay flags "obvious fix" findings still need user approval', () => {
const out = generateModelOverlay(makeCtx('opus-4-8'));
expect(out).toMatch(/obvious fix/i);
expect(out).toMatch(/user approval/i);
});
test('resolved overlay keeps Effort-match / Literal interpretation nudges', () => {
const out = generateModelOverlay(makeCtx('opus-4-8'));
expect(out).toContain('Effort-match the step');
expect(out).toContain('Literal interpretation awareness');
});
test('claude overlay (no INHERIT chain) does not carry the pacing directive', () => {
// Claude is the default overlay; opus-4-8 inherits FROM claude.
// The pacing directive belongs to the opus-4-x overlays only.
const out = generateModelOverlay(makeCtx('claude'));
expect(out).not.toContain('Pace questions to the skill');
});
});

View File

@ -0,0 +1,56 @@
/**
* Sonnet 5 model overlay gate-tier assertions on the family nudges.
*
* sonnet-5 inherits the claude base and adds Sonnet-5 family nudges: literal
* instruction following (state scope explicitly), scope work to the request
* (raise effort rather than prompting around shallow reasoning), and
* verbosity that tracks task complexity.
*/
import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import type { TemplateContext } from '../scripts/resolvers/types';
import { HOST_PATHS } from '../scripts/resolvers/types';
import { generateModelOverlay } from '../scripts/resolvers/model-overlay';
function makeCtx(model: string): TemplateContext {
return {
skillName: 'test-skill',
tmplPath: 'test.tmpl',
host: 'claude',
paths: HOST_PATHS.claude,
preambleTier: 2,
model,
};
}
const ROOT = path.resolve(__dirname, '..');
describe('Sonnet 5 overlay — family nudges', () => {
test('raw sonnet-5.md contains the literal-instructions nudge', () => {
const raw = fs.readFileSync(path.join(ROOT, 'model-overlays/sonnet-5.md'), 'utf-8');
expect(raw).toContain('Instructions are read literally');
});
test('resolved overlay inherits from claude base (INHERIT:claude)', () => {
const out = generateModelOverlay(makeCtx('sonnet-5'));
expect(out).toContain('Todo-list discipline');
expect(out).toContain('subordinate');
});
test('resolved overlay carries the Sonnet 5 nudges', () => {
const out = generateModelOverlay(makeCtx('sonnet-5'));
expect(out).toContain('Instructions are read literally');
expect(out).toContain('Scope work to the request');
});
test('resolved overlay has no unresolved INHERIT directive', () => {
const out = generateModelOverlay(makeCtx('sonnet-5'));
expect(out).not.toContain('{{INHERIT:');
});
test('claude overlay (base) does not carry the Sonnet 5 nudge', () => {
const out = generateModelOverlay(makeCtx('claude'));
expect(out).not.toContain('Instructions are read literally');
});
});