Skip to content

Instantly share code, notes, and snippets.

@johnlindquist
Created December 8, 2025 17:30
Show Gist options
  • Select an option

  • Save johnlindquist/849b813e76039a908d962b2f0923dc9a to your computer and use it in GitHub Desktop.

Select an option

Save johnlindquist/849b813e76039a908d962b2f0923dc9a to your computer and use it in GitHub Desktop.
Claude Code Context Optimization: 54% reduction in initial tokens while maintaining full tool access

Claude Code Context Optimization: A Complete Guide

Date: December 8, 2025 Goal: Reduce initial context consumption while maintaining full tool access and consistent Claude behavior


Executive Summary

This project achieved a 54% reduction in initial context (7,584 → 3,434 tokens) while improving tool discovery and enforcement. The key insight: Claude doesn't need verbose documentation upfront—it needs triggers to know when to load detailed context.

Results at a Glance

Metric Before After Reduction
Initial context tokens 7,584 3,434 54%
skills-rules.md 10,204 bytes 2,997 bytes 70%
identity.md + simulator.md 6,843 bytes 1,252 bytes 82%
logging-preferences.md 4,887 bytes 1,084 bytes 78%
Compressed skills (optional) 244 KB 17 KB 93%

The Problem

Claude Code's context window is precious. Every token in CLAUDE.md and @imported files consumes space that could be used for actual work. Our setup had:

  1. Verbose skill documentation - Full protocols loaded even when not needed
  2. Duplicate content - Simulator paradigm explained in multiple places
  3. Redundant hook injections - bd ready running on every prompt
  4. No trigger routing - Claude had to read everything to know what tools to use

Architecture Overview

Before: Monolithic Context Loading

CLAUDE.md (loaded at session start)
├── identity.md (2,156 bytes)
├── simulator-paradigm.md (4,687 bytes)  ← REDUNDANT
├── facts.md
├── root.md
├── skills-rules.md (10,204 bytes)       ← VERBOSE
├── preferences.md
├── cli-preferences.md
├── scripting-preferences.md
├── git-preferences.md
├── logging-preferences.md (4,887 bytes) ← VERBOSE
└── root-cause-protocol.md

After: Tiered Context with Lazy Loading

CLAUDE.md (minimal)
├── identity.md (1,252 bytes)            ← CONSOLIDATED
├── facts.md
├── root.md
├── skills-rules.md (2,997 bytes)        ← TRIGGER TABLE ONLY
├── preferences.md
├── cli-preferences.md
├── scripting-preferences.md
├── git-preferences.md
├── logging-preferences.md (1,084 bytes) ← RULES ONLY
└── root-cause-protocol.md

Skills loaded on-demand via Skill("name")
├── beads/SKILL.md      ← Only when task tracking needed
├── investigate/SKILL.md ← Only when debugging
├── council/SKILL.md    ← Only for multi-agent queries
└── ... 30+ skills

Changes Made

1. skills-rules.md Compression (70% reduction)

Before: Full protocol documentation for every skill (~10KB) After: Minimal trigger table with tool references (~3KB)

## Skill Trigger Table

| Triggers | Skill | Primary Tools |
|----------|-------|---------------|
| task, track, backlog, blocked by | **beads** | `beads_ready`, `beads_add` |
| search, research, look up, latest | gemini-research | `gemini_research` |
| review this, what's wrong | review/deep-review | Expert personas |
| ...

Key principle: Claude only needs to know when to invoke a skill. The skill's SKILL.md file contains the detailed protocol, loaded on-demand.

2. Identity Consolidation (82% reduction)

Before: Two separate files

  • identity.md (2,156 bytes) - Core identity
  • simulator-paradigm.md (4,687 bytes) - Detailed simulation philosophy

After: Single consolidated file (1,252 bytes)

## Core Identity

You are a **simulator**, not an entity with opinions. When asked subjective
questions, channel specific perspectives rather than generating averaged responses.

### Default Personas (invoke via Skill)
- **Architecture**: Martin Fowler, Uncle Bob
- **Performance**: John Carmack, Casey Muratori
- **Systems**: Linus Torvalds, Bryan Cantrill

3. Logging Preferences (78% reduction)

Before: Verbose examples and edge cases (4,887 bytes) After: Hard rules only, with pointer to Skill("investigate") (1,084 bytes)

## Hard Rules
1. NEVER add console.log to production code without cleanup plan
2. Use structured logging (pino/winston) for services
3. Log at boundaries: API entry, external calls, errors

For debugging workflows → Skill("investigate")

4. Registry Triggers (New Feature)

Extended ~/dev/cm/server/registry/definitions.ts:

export interface ToolDefinition {
  name: string;
  description: string;
  category: string;
  inputSchema: Tool["inputSchema"];
  handler: (args: Record<string, unknown>) => Promise<unknown>;

  // NEW FIELDS
  triggers?: string[];        // Phrases that route to this tool
  skill?: string;             // Associated skill for context
  replacesNative?: string;    // Native tool this replaces
}

Example tool with triggers:

{
  name: "beads_add",
  description: "Create a new issue/task in the beads tracker",
  category: "beads",
  triggers: ["track", "task", "todo", "backlog", "create issue"],
  skill: "beads",
  replacesNative: "TodoWrite",
  // ...
}

5. Sync Script Enhancement

Updated ~/dev/cm/scripts/sync-tool-list.ts to export:

const output = {
  generated: new Date().toISOString(),
  total: toolRegistry.length,
  categories: sortedCategories,
  triggerIndex,        // NEW: trigger → [tool names]
  nativeReplacements,  // NEW: nativeTool → cmTool
  tools,
}

Output file: ~/.claude/hooks/cm-tools.json

This enables:

  • PreToolUse hook can check triggers before allowing native tools
  • SessionStart can inject relevant tools based on project type
  • Skills can be auto-suggested based on user prompt keywords

6. Skill Compression Script (93% reduction)

Created ~/.claude/scripts/compress-skills.ts:

// Extracts minimal context from SKILL.md files
// Input:  skills/beads/SKILL.md (12KB)
// Output: skills-compressed/beads.md (800 bytes)

interface CompressedSkill {
  name: string;
  triggers: string[];
  tools: string[];
  quickStart: string;  // First 3 sentences only
}

Usage:

bun run ~/.claude/scripts/compress-skills.ts

7. Context Flooding Fix

Bug: UserPromptSubmit.ts ran bd ready on every prompt, injecting full beads status repeatedly.

Before:

if (gitInitialized && beadsInitialized) {
    const result = await Bun.$`bd ready`.nothrow().quiet()
    messages.push(`<beads><status>${stdout}</status></beads>`)
}

After:

// NOTE: Removed bd ready injection here - SessionStart already provides this once.
// Injecting on every prompt was flooding context.

Implementation Patterns

Pattern 1: Trigger Tables Over Documentation

Instead of explaining what a skill does, list when to use it:

❌ Bad (verbose):
## beads
Beads is a dependency-aware issue tracking system that integrates with git...
[500 words of explanation]

✅ Good (triggers):
| Triggers | Skill |
|----------|-------|
| task, track, backlog, blocked by, depends on | beads |

Pattern 2: Lazy Loading via Skills

❌ Bad (always loaded):
@~/.claude/beads-full-protocol.md

✅ Good (loaded on-demand):
For detailed beads protocol → Skill("beads")

Pattern 3: Registry as Source of Truth

❌ Bad (manual sync):
- Update skills-rules.md
- Update CLAUDE.md
- Update hooks
- Hope they stay in sync

✅ Good (generated):
1. Define tool in registry with triggers
2. Run sync-tool-list.ts
3. All consumers read from cm-tools.json

Pattern 4: Enforce via Hooks, Not Instructions

❌ Bad (instructions):
"NEVER use WebSearch, use gemini_web_search instead"

✅ Good (hook enforcement):
PreToolUse hook blocks WebSearch and suggests alternative

File Reference

Modified Files

File Change Size Δ
~/.claude/CLAUDE.md Removed simulator-paradigm.md import -1 line
~/.claude/skills-rules.md Compressed to trigger table -7,207 bytes
~/.claude/identity.md Consolidated with simulator paradigm -5,591 bytes
~/.claude/logging-preferences.md Rules only, no examples -3,803 bytes
~/.claude/hooks/UserPromptSubmit.ts Removed duplicate bd ready -4 lines
~/dev/cm/server/registry/definitions.ts Added triggers/skill/replacesNative +15 lines
~/dev/cm/scripts/sync-tool-list.ts Export triggerIndex +20 lines

New Files

File Purpose
~/.claude/scripts/compress-skills.ts Generate compressed skill stubs
~/.claude/hooks/cm-tools.json Tool metadata with triggers
~/.claude/hooks/cm-tools-reference.md Human-readable tool list

Verification

Check Initial Context Size

# Count bytes in all @imported files
wc -c ~/.claude/{identity,facts,root,skills-rules,preferences,cli-preferences,scripting-preferences,git-preferences,logging-preferences,root-cause-protocol}.md

# Estimate tokens (bytes / 4)

Test Trigger Routing

# Check cm-tools.json for trigger index
cat ~/.claude/hooks/cm-tools.json | jq '.triggerIndex'

Verify Hook Behavior

# Start new Claude session and submit basic prompt
# Context should NOT flood with beads status on every message

Future Optimizations

Not Yet Implemented

  1. Dynamic context loading - Hooks inject skill context based on prompt keywords
  2. Project-type detection - Auto-load relevant tools based on package.json/Cargo.toml
  3. Context budget tracking - Warn when approaching limits
  4. Skill dependency graph - Load related skills together

Considered but Rejected

  1. Removing all @imports - Too aggressive, some context always needed
  2. External documentation only - Claude needs some grounding in-context
  3. Per-project CLAUDE.md - Maintenance burden outweighs benefits

Conclusion

Context optimization is about lazy loading, not removal. Claude needs to know:

  1. What tools exist → Minimal list with categories
  2. When to use them → Trigger phrases
  3. How to use them → Load detailed docs on-demand via Skill()

The 54% reduction in initial context means more room for actual work, faster responses, and lower costs—without sacrificing capability.


Generated from optimization work on December 8, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment