Claude Fable 5 is Anthropic's most capable model for autonomous, long-horizon work. Released June 9 2026 — and fully restored after a brief export-control suspension on July 1 2026 — it brings a 1-million-token context window, up to 128k output tokens per response, and native extended thinking to the Messages API. With usage-based pricing switching over on July 7 2026, there has never been a better moment to start building with it.
In this tutorial you will build a TypeScript CLI agent that performs a deep code review of any project directory. The agent reads files with custom tools, applies extended thinking to reason about security and architecture, and uses prompt caching to cut API costs by up to 90%. This is the "whole-job delegation" pattern Fable 5 was designed for: you hand it a complex task, set a few guardrails, and let it work.
Prerequisites
- Node.js 20+ — verify with
node --version - TypeScript familiarity and
async/awaitcomfort - An Anthropic API key — generate one at console.anthropic.com
- A code editor (VS Code recommended)
No GPU hardware is needed. All inference runs on Anthropic's hosted API.
What You'll Build
By the end of this tutorial you will have a CLI command:
npx tsx review-agent.ts ./my-projectIt launches a Fable 5 agent that autonomously explores your codebase using file-system tools, applies extended thinking to spot security vulnerabilities, and streams a structured report with Critical, High, Medium, and Recommendation sections.
The complete agent is roughly 250 lines of TypeScript — small enough to fully understand, real enough to use in production.
Understanding Claude Fable 5
A short model overview before writing any code.
What makes Fable 5 different from Sonnet 5
| Claude Sonnet 5 | Claude Fable 5 | |
|---|---|---|
| Context window | 200k tokens | 1M tokens |
| Max output | 64k tokens | 128k tokens |
| Best for | Fast, iterative responses | Long, multi-step autonomous tasks |
| Input pricing | $3 / MTok | $10 / MTok |
| Output pricing | $15 / MTok | $50 / MTok |
| Cached input | $0.30 / MTok | $1 / MTok |
Use Sonnet 5 for interactive features. Use Fable 5 when you need an agent that plans across many steps, delegates sub-tasks, checks its own work, and produces a coherent result over dozens of tool calls.
Pricing note
Fable 5 API calls use usage-based billing starting July 7 2026 — no subscription credit required. At $10 per million input tokens with prompt caching giving 90% off cached reads, a thorough audit of a 50-file TypeScript project typically costs between $0.20 and $0.80 depending on how many turns the agent takes.
Step 1: Project Setup
mkdir fable5-reviewer && cd fable5-reviewer
npm init -y
npm install @anthropic-ai/sdk dotenv
npm install -D typescript tsx @types/nodeCreate tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
}
}Create .env:
ANTHROPIC_API_KEY=sk-ant-...
Never commit .env — add it to .gitignore immediately.
Step 2: Verify Streaming Works with Fable 5
Non-streaming calls to Fable 5 can take 60 seconds or more on complex prompts. Always stream. Here is a minimal test:
// stream-test.ts
import Anthropic from '@anthropic-ai/sdk';
import 'dotenv/config';
const client = new Anthropic();
async function main(): Promise<void> {
const stream = client.messages.stream({
model: 'claude-fable-5',
max_tokens: 512,
messages: [
{
role: 'user',
content: 'List 5 things to check in a TypeScript security audit. Be brief.',
},
],
});
for await (const chunk of stream) {
if (
chunk.type === 'content_block_delta' &&
chunk.delta.type === 'text_delta'
) {
process.stdout.write(chunk.delta.text);
}
}
const final = await stream.finalMessage();
console.log(
`\n\nTokens: ${final.usage.input_tokens} in / ${final.usage.output_tokens} out`
);
}
main();Run it:
npx tsx stream-test.tsYou should see the list stream in token by token. If you see model_not_available, check that access has been re-enabled on your account at console.anthropic.com.
Step 3: Define Custom File-System Tools
Tools are how Fable 5 reads your project. Define three: list_directory, read_file, and search_pattern.
// tools.ts
import { readFileSync, readdirSync, statSync } from 'fs';
import { join, extname } from 'path';
import type Anthropic from '@anthropic-ai/sdk';
const ALLOWED_EXTENSIONS = new Set([
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
'.json', '.md', '.yaml', '.yml', '.toml', '.env.example',
]);
export const toolDefinitions: Anthropic.Tool[] = [
{
name: 'list_directory',
description: 'List files and directories at a given path.',
input_schema: {
type: 'object' as const,
properties: {
path: { type: 'string', description: 'Directory path to list' },
recursive: { type: 'boolean', description: 'Recurse into subdirectories' },
},
required: ['path'],
},
},
{
name: 'read_file',
description: 'Read the text content of a source file.',
input_schema: {
type: 'object' as const,
properties: {
path: { type: 'string', description: 'Absolute or relative file path' },
},
required: ['path'],
},
},
{
name: 'search_pattern',
description: 'Search for a regex pattern across all source files in a directory. Returns matching lines with file and line number.',
input_schema: {
type: 'object' as const,
properties: {
pattern: { type: 'string', description: 'Regex pattern to search for' },
directory: { type: 'string', description: 'Root directory to search' },
maxResults: {
type: 'number',
description: 'Maximum lines to return (default 50)',
},
},
required: ['pattern', 'directory'],
},
},
];
function walkDir(dir: string, recursive: boolean): string[] {
const results: string[] = [];
for (const name of readdirSync(dir)) {
if (name.startsWith('.') || name === 'node_modules' || name === 'dist') continue;
const full = join(dir, name);
const stat = statSync(full);
if (stat.isDirectory()) {
results.push(`${full}/`);
if (recursive) results.push(...walkDir(full, true));
} else {
results.push(full);
}
}
return results;
}
function grepFiles(pattern: string, dir: string, maxResults: number): string[] {
const regex = new RegExp(pattern, 'gi');
const matches: string[] = [];
const files = walkDir(dir, true).filter(
(f) => !f.endsWith('/') && ALLOWED_EXTENSIONS.has(extname(f))
);
for (const file of files) {
if (matches.length >= maxResults) break;
try {
const lines = readFileSync(file, 'utf-8').split('\n');
for (let i = 0; i < lines.length; i++) {
if (regex.test(lines[i])) {
matches.push(`${file}:${i + 1}: ${lines[i].trim()}`);
if (matches.length >= maxResults) break;
}
}
} catch {
// skip unreadable files
}
}
return matches;
}
export function executeTool(
name: string,
input: Record<string, unknown>
): string {
try {
if (name === 'list_directory') {
const path = input.path as string;
const recursive = (input.recursive as boolean) ?? false;
const entries = walkDir(path, recursive);
return entries.slice(0, 300).join('\n') || '(empty directory)';
}
if (name === 'read_file') {
const path = input.path as string;
if (!ALLOWED_EXTENSIONS.has(extname(path))) {
return `Skipped: extension ${extname(path)} is not in the allow-list.`;
}
const content = readFileSync(path, 'utf-8');
// Truncate files larger than 50k chars to protect the context budget
return content.length > 50_000
? content.slice(0, 50_000) + '\n\n[file truncated — over 50 000 characters]'
: content;
}
if (name === 'search_pattern') {
const pattern = input.pattern as string;
const directory = input.directory as string;
const maxResults = (input.maxResults as number) ?? 50;
const hits = grepFiles(pattern, directory, maxResults);
return hits.length > 0 ? hits.join('\n') : 'No matches found.';
}
return `Unknown tool: ${name}`;
} catch (err) {
return `Tool error: ${(err as Error).message}`;
}
}The 50k character truncation per file is a deliberate safeguard. Even with Fable 5's 1M token context, a single enormous generated file could consume tens of thousands of tokens. Trim aggressively; the agent will ask for more context if needed.
Step 4: The Core Agent Loop
The loop drives Fable 5 until it returns a final answer. On each turn: call the API, dispatch any tool calls, feed results back, repeat.
// agent-loop.ts
import Anthropic from '@anthropic-ai/sdk';
import { toolDefinitions, executeTool } from './tools.js';
const client = new Anthropic();
const SYSTEM = `You are a senior software engineer performing a thorough, systematic code review.
Workflow:
1. Call list_directory on the project root (recursive: true) to understand the structure.
2. Read key config files first: package.json, tsconfig.json, .eslintrc, next.config.js.
3. Read each major source file in turn.
4. Use search_pattern to look for common security issues: eval, dangerouslySetInnerHTML, SQL string building, hardcoded secrets, process.env without validation.
5. After reading all relevant files, write a structured report.
Report format:
## Critical Issues
## High Priority
## Medium Priority
## Recommendations`;
export async function runReviewAgent(projectPath: string): Promise<void> {
const messages: Anthropic.MessageParam[] = [
{
role: 'user',
content: `Perform a comprehensive code review of the project at: ${projectPath}
Review everything — security, correctness, TypeScript quality, error handling, and architecture. Start by exploring the project structure.`,
},
];
let iteration = 0;
const maxIterations = 40;
console.log(`\nFable 5 code review — ${projectPath}\n${'─'.repeat(60)}\n`);
while (iteration < maxIterations) {
iteration++;
const response = await client.messages.create({
model: 'claude-fable-5',
max_tokens: 8192,
system: SYSTEM,
tools: toolDefinitions,
messages,
});
// Add the assistant turn before processing
messages.push({ role: 'assistant', content: response.content });
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === 'text' && block.text) {
process.stdout.write(block.text);
} else if (block.type === 'tool_use') {
const input = block.input as Record<string, unknown>;
console.log(`\n [${block.name}] ${JSON.stringify(input).slice(0, 80)}`);
const result = executeTool(block.name, input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
}
}
if (toolResults.length > 0) {
messages.push({ role: 'user', content: toolResults });
continue;
}
if (response.stop_reason === 'end_turn') {
console.log(`\n${'─'.repeat(60)}\nDone — ${iteration} iterations`);
break;
}
console.warn(`\nUnexpected stop_reason: ${response.stop_reason}`);
break;
}
if (iteration >= maxIterations) {
console.warn('Max iterations reached — review may be incomplete.');
}
}Fable 5 typically finishes a medium TypeScript project review in 8–18 iterations. The 40-iteration cap is a safety backstop for very large monorepos.
Step 5: Extended Thinking for Deep Security Analysis
Extended thinking lets Fable 5 reason privately before writing its final response. For security analysis this matters: vulnerability chains require more careful reasoning than surface-level pattern matching. Enable it with the thinking parameter:
// deep-review.ts
import Anthropic from '@anthropic-ai/sdk';
import { toolDefinitions, executeTool } from './tools.js';
const client = new Anthropic();
export async function runDeepSecurityAudit(projectPath: string): Promise<void> {
const messages: Anthropic.MessageParam[] = [
{
role: 'user',
content: `Perform a deep security audit of ${projectPath}.
Focus on:
- Injection vulnerabilities (SQL, command, path traversal)
- Authentication and authorization flaws
- Sensitive data exposure and secrets in code
- Insecure deserialization
- Vulnerable dependencies in package.json
Examine all source files carefully, then provide a detailed security report.`,
},
];
let iteration = 0;
while (iteration < 30) {
iteration++;
const response = await client.messages.create({
model: 'claude-fable-5',
max_tokens: 16000,
tools: toolDefinitions,
messages,
thinking: {
type: 'enabled',
budget_tokens: 8000, // Allow up to 8k tokens of private reasoning per turn
},
});
messages.push({ role: 'assistant', content: response.content });
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === 'thinking') {
// You can display or omit thinking blocks — they are never billed at output rates
console.log(`\n [thinking — ${block.thinking.length} chars]`);
} else if (block.type === 'text' && block.text) {
process.stdout.write(block.text);
} else if (block.type === 'tool_use') {
console.log(`\n [${block.name}]`);
const result = executeTool(
block.name,
block.input as Record<string, unknown>
);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
}
}
if (toolResults.length > 0) {
messages.push({ role: 'user', content: toolResults });
continue;
}
if (response.stop_reason === 'end_turn') break;
}
}A budget_tokens value of 8000 is a good starting point. For security audits, values between 4000 and 16 000 are practical — larger budgets enable deeper reasoning chains but add to token count. Thinking tokens are billed at input rates, not output rates.
Step 6: Prompt Caching for 90% Cost Savings
At $10 per million input tokens, a 40-turn agent run on a large project can accumulate significant input costs — the full conversation history is sent on every turn. Prompt caching stores static blocks on Anthropic's servers for 5 minutes, billing those reads at just $1 per million tokens.
Cache your system prompt and any large reference file you read once and reference repeatedly:
// cached-agent.ts
import Anthropic from '@anthropic-ai/sdk';
import { readFileSync } from 'fs';
import { toolDefinitions, executeTool } from './tools.js';
const client = new Anthropic();
export async function runCachedReview(projectPath: string): Promise<void> {
// Read package.json once — it will be cached across all subsequent turns
let packageJson = '(no package.json)';
try {
packageJson = readFileSync(`${projectPath}/package.json`, 'utf-8');
} catch { /* not found */ }
// System prompt with a cacheable block for the large reference content
const systemContent: Anthropic.TextBlockParam[] = [
{
type: 'text',
text: `You are a code security and quality reviewer.\n\nproject package.json:\n\`\`\`json\n${packageJson}\n\`\`\`\n\nReference this when analysing dependencies.`,
cache_control: { type: 'ephemeral' }, // Cache this large block for 5 minutes
},
{
type: 'text',
text: 'Use the provided tools to read source files. Report issues by severity.',
// Shorter blocks are not worth caching
},
];
const messages: Anthropic.MessageParam[] = [
{
role: 'user',
content: `Review all TypeScript files in ${projectPath} for security and code quality issues.`,
},
];
let iteration = 0;
let cachedTokensSaved = 0;
while (iteration < 30) {
iteration++;
const response = await client.messages.create({
model: 'claude-fable-5',
max_tokens: 8192,
system: systemContent,
tools: toolDefinitions,
messages,
});
const usage = response.usage as Anthropic.Usage & {
cache_read_input_tokens?: number;
};
if (usage.cache_read_input_tokens) {
cachedTokensSaved += usage.cache_read_input_tokens;
}
messages.push({ role: 'assistant', content: response.content });
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === 'text') process.stdout.write(block.text);
else if (block.type === 'tool_use') {
const result = executeTool(
block.name,
block.input as Record<string, unknown>
);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
}
}
if (toolResults.length > 0) {
messages.push({ role: 'user', content: toolResults });
continue;
}
if (response.stop_reason === 'end_turn') {
// 9/10 of cached tokens cost nothing compared to standard input rate
const savedUsd = ((cachedTokensSaved * 9) / 10 / 1_000_000) * 10;
console.log(
`\n\nCache saved ~$${savedUsd.toFixed(4)} (${cachedTokensSaved.toLocaleString()} tokens at 90% discount)`
);
break;
}
}
}Prompt caching pays off most on blocks that appear unchanged across many turns — system prompts, large reference documents, and shared configuration files. The 5-minute cache window easily covers a complete agent run.
Step 7: The Entry-Point CLI
Wire everything into a single runnable file:
// review-agent.ts
import 'dotenv/config';
import { runCachedReview } from './cached-agent.js';
const projectPath = process.argv[2];
if (!projectPath) {
console.error('Usage: npx tsx review-agent.ts <path>');
process.exit(1);
}
console.log('Claude Fable 5 Code Reviewer');
console.log(`Path : ${projectPath}`);
console.log('Model : claude-fable-5');
console.log('Context: 1M tokens | Max output: 128k tokens\n');
runCachedReview(projectPath).catch((err) => {
console.error('\nAgent error:', err.message);
process.exit(1);
});Run a review:
npx tsx review-agent.ts ./srcFable 5 will explore the directory structure, read files one by one, search for security patterns, and write the final report — typically within 2–5 minutes for a medium TypeScript project.
Troubleshooting
Streaming timeout — Always use client.messages.stream() when extended thinking is enabled. Non-streaming calls with large budget_tokens values will time out at the HTTP layer before the model finishes.
model_not_available — Fable 5 access was restored on July 1 2026 after the June export-control suspension. If you still see this error, check your account tier at console.anthropic.com — Fable 5 requires a usage-credit-enabled API key.
Context length exceeded — With 1M tokens this is rare, but very large monorepos with many lock files and generated code can hit it. Add '.lock', '.snap', '.d.ts' to the blocked-extension list in tools.ts.
High cost on first run — The first turn of a run does not benefit from cache (there is nothing cached yet). Costs drop by up to 90% from the second turn onward. Profile with a short test run on a small directory before running on a large codebase.
Next Steps
- Add a file-write tool — let the agent save the report as
code-review.mdin the project root - GitHub Actions integration — run the agent automatically on each pull request and post findings as PR comments
- Specialist sub-agents — use the Beta Managed Agents API (
client.beta.environments.work.worker()) to spin up parallel security, performance, and style reviewers that report back to a Fable 5 orchestrator - Batch API for large codebases — use the Messages Batch API for cost-effective analysis of many repos at once; batch pricing is $5 / $25 per million input / output tokens
Conclusion
Claude Fable 5 changes what a single TypeScript agent can do in one uninterrupted run. The 1-million-token context window eliminates the context juggling that makes long-horizon agents brittle. Extended thinking brings careful, multi-step reasoning to security analysis. And prompt caching turns what could be an expensive iterative process into something cost-effective enough to run on every pull request.
The patterns here — streaming first, capping iterations, caching static blocks — are the right foundation for any Fable 5-powered workflow. With usage-based pricing now live, the model is accessible without a subscription tier gate. Start small, measure token usage on real code, then scale up to the tasks that would previously have required a human reviewer sitting at a terminal for hours.