Context Tree-Shaking
Introduction
Traditional MCP servers put domain rules in the system prompt: "amounts are in cents", "use USD format", "mask emails for non-admins". These rules are sent on every single turn, regardless of whether the conversation involves invoices, emails, or users.
Context Tree-Shaking eliminates this waste. Rules are attached to Presenters and travel with the data — they appear only when the entity is in the response. No invoices in the response? No invoice rules in the context window.
The System Prompt Problem
A typical enterprise system prompt:
System: You are a helpful assistant for Acme Corp.
- Invoice amounts are in CENTS. Divide by 100 for display.
- Use USD currency format: $XX,XXX.00
- Employee salaries are confidential. Never show to non-admins.
- Project budgets should be shown in monthly breakdown.
- Order timestamps use UTC. Convert to user's timezone.
- Customer emails must be masked for external agents.
... (50 more rules)This costs ~500 tokens per turn, even when the user asks "What's the weather?" None of these rules are relevant.
JIT Rules Delivery
With Tree-Shaking, rules are declared on each Presenter and delivered Just-In-Time:
const InvoicePresenter = createPresenter('Invoice')
.schema({
id: t.string,
amount_cents: t.number.describe('Value in CENTS. Divide by 100.'),
status: t.enum('paid', 'pending', 'overdue'),
})
.rules(['Always show currency as USD. Format: $XX,XXX.00']);
const EmployeePresenter = createPresenter('Employee')
.schema({
id: t.string,
name: t.string,
salary: t.number,
})
.rules((emp, ctx) => [
ctx?.user?.role !== 'admin'
? 'RESTRICTED: Do NOT display salary information.'
: null,
]);When the agent calls billing.get_invoice, it receives invoice rules. When it calls employees.get, it receives employee rules. When it calls projects.list, it receives neither — zero wasted tokens.
How It Works
Turn 1: "Show me invoice INV-001"
→ billing.get_invoice → InvoicePresenter →
Rules: ["Value in CENTS. Divide by 100.", "Format: $XX,XXX.00"]
Tokens used for rules: ~30
Turn 2: "List all projects"
→ projects.list → ProjectPresenter →
Rules: ["Budget is monthly."]
Tokens used for rules: ~5
Turn 3: "What's 2 + 2?"
→ No tool called →
Rules: none
Tokens used for rules: 0Compare with the system prompt approach: ~500 tokens for rules on every turn, regardless of relevance.
Combining with .describe()
Zod .describe() annotations are auto-extracted as rules. Combined with explicit .rules(), you get layered context:
const OrderPresenter = createPresenter('Order')
.schema({
id: t.string,
total: t.number.describe('Total in CENTS (USD). Divide by 100.'),
status: t.enum('processing', 'shipped', 'delivered')
.describe('Use emoji: 🔄 processing, 📦 shipped, ✅ delivered'),
shipped_at: t.nullable(t.string)
.describe('UTC timestamp. Convert to user timezone.'),
})
.rules(['Always show tracking link for shipped orders.']);The AI receives all four rules (three auto-extracted from .describe(), one explicit) — but only when order data is in the response. On all other turns, these rules cost zero tokens.