Project 3: Context Window Visualizer
Understanding Token Budgets: See what the AI sees, master what it remembers
Project Metadata
| Attribute | Value |
|---|---|
| Difficulty | Level 2: Intermediate |
| Time Estimate | 1-2 Weeks (20-30 hours) |
| Primary Language | TypeScript |
| Alternative Languages | Python, Rust |
| Prerequisites | Projects 1-2, TypeScript, terminal UI familiarity |
| Main Reference | “AI Engineering” by Chip Huyen |
Learning Objectives
By completing this project, you will:
- Understand tokenization deeply - how text becomes tokens, why some content is “expensive”
- Visualize context consumption - see in real-time what’s filling your context window
- Master context management strategies - when to clear, compact, or restructure
- Build terminal UIs - create interactive dashboards using Ink or Blessed
- Develop intuition for AI memory - understand what the AI “sees” vs. what it “forgets”
Deep Theoretical Foundation
What Is a Context Window?
The context window is the AI’s working memory - everything it can “see” at once when generating a response. Unlike human memory which fades gradually, the context window has a hard boundary: either content is in the window and fully visible, or it’s outside and completely invisible.
┌─────────────────────────────────────────────────────────────────────┐
│ THE CONTEXT WINDOW METAPHOR │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Human Memory AI Context Window │
│ ──────────── ───────────────── │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Recent memories │ │ IN WINDOW │ │
│ │ (clear, detailed) │ │ (perfectly visible) │ │
│ │ │ │ │ │
│ │ Older memories │ ├─────────────────────┤ │
│ │ (fuzzy, incomplete) │ │ OUT OF WINDOW │ │
│ │ │ │ (completely gone) │ │
│ │ Distant memories │ │ │ │
│ │ (fragments only) │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ Gradient decay Binary cutoff │
│ │
│ This is why context management matters: │
│ • AI has perfect recall of in-window content │
│ • But ZERO recall of out-of-window content │
│ • Strategic loading is essential │
│ │
└─────────────────────────────────────────────────────────────────────┘
Tokens: The Currency of Context
Tokens are the atomic units of AI processing. Understanding tokenization is crucial because it’s often counterintuitive:
┌─────────────────────────────────────────────────────────────────────┐
│ TOKENIZATION EXAMPLES │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ TEXT TOKENS COUNT SURPRISE? │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ "Hello" [Hello] 1 No │
│ "Hello, world!" [Hello][,] 4 Punctuation │
│ [world][!] adds tokens │
│ │
│ "authentication" [authentic] 2 Long words │
│ [ation] split │
│ │
│ " " (4 spaces) [ ] 1 Whitespace │
│ efficient │
│ │
│ "café" [caf][é] 2 Non-ASCII │
│ expensive │
│ │
│ "const x = 42;" [const][ x] 6 Code is │
│ [ =][ 42] dense │
│ [;] │
│ │
│ JSON with formatting Many High Whitespace │
│ (indentation, newlines) tokens in JSON │
│ adds up │
│ │
│ Minified JSON Fewer Lower Compact is │
│ tokens cheaper │
│ │
└─────────────────────────────────────────────────────────────────────┘
The Context Budget Model
Think of context like a financial budget:
┌─────────────────────────────────────────────────────────────────────┐
│ CONTEXT BUDGET ALLOCATION │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ TOTAL BUDGET: 200,000 tokens │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ FIXED COSTS (Cannot be reduced) │ │
│ │ ────────────────────────────── │ │
│ │ │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ System Prompt: ~2,000 │ ← Kiro's core instructions │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ Agent Config: ~500 │ ← Your agent's persona │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
│ │ VARIABLE COSTS (Your control) │ │
│ │ ──────────────────────────── │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Steering Rules: 1,000 - 10,000 │ │ │
│ │ │ (depends on how many .md files in .kiro/steering/) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Context Files: 0 - 100,000+ │ │ │
│ │ │ (files you /context add) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Chat History: 0 - remainder │ │ │
│ │ │ (grows with each exchange) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ BUDGET RULE: │
│ Fixed Costs + Variable Costs <= 200,000 │
│ │
│ When you exceed: Oldest chat history is dropped (silently!) │
│ │
└─────────────────────────────────────────────────────────────────────┘
The Hidden Dangers of Context Overflow
When you exceed the context limit, Kiro doesn’t error - it silently drops old content:
┌─────────────────────────────────────────────────────────────────────┐
│ CONTEXT OVERFLOW BEHAVIOR │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Before Overflow (195,000/200,000 tokens): │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ [Turn 1] User: "Let's refactor the auth system..." │ │
│ │ [Turn 1] AI: "I'll analyze the current implementation..." │ │
│ │ [Turn 2] User: "Focus on the OAuth flow" │ │
│ │ [Turn 2] AI: "The OAuth flow has these components..." │ │
│ │ [Turn 3] User: "Implement the new design" │ │
│ │ ... (many more turns) ... │ │
│ │ [Turn 50] Current context │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ After Overflow (adding 10,000 tokens): │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ╳ [Turn 1] DROPPED - AI forgets initial context │ │
│ │ ╳ [Turn 2] DROPPED - AI forgets OAuth focus │ │
│ │ ✓ [Turn 3] User: "Implement the new design" │ │
│ │ ✓ ... (remaining turns) ... │ │
│ │ ✓ [Turn 50] Current context │ │
│ │ ✓ [Turn 51] New content │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ RESULT: AI implements "new design" but forgot what it was! │
│ │
│ This is why monitoring is critical: │
│ • You can't see what the AI has forgotten │
│ • The AI doesn't know it forgot │
│ • Only proactive monitoring prevents this │
│ │
└─────────────────────────────────────────────────────────────────────┘
Real-World Analogy: The Whiteboard
Imagine a whiteboard in a meeting room:
- Whiteboard size = Context window limit
- Marker writing = Tokens
- Erasing old content = Context overflow dropping
If you keep writing without ever erasing, eventually you run out of space and must erase the oldest content - even if it was important. The context visualizer is like having a camera on the whiteboard that tracks exactly what’s written and warns you before important content gets erased.
Historical Context: From 4K to 200K
Context window sizes have grown dramatically:
| Year | Model | Context Window |
|---|---|---|
| 2020 | GPT-2 | 1,024 tokens |
| 2022 | GPT-3.5 | 4,096 tokens |
| 2023 | GPT-4 | 8,192 tokens |
| 2024 | Claude 3 | 100,000 tokens |
| 2025 | Claude 4.5 | 200,000 tokens |
Despite this growth, context remains finite. The 200K window feels infinite until you load a large codebase - then it fills quickly.
Complete Project Specification
What You Are Building
A Real-time Context Monitor that:
- Displays Live Token Usage: Shows current consumption as a progress bar
- Attributes Tokens to Sources: Breaks down usage by chat history, files, steering
- Lists Active Context Files: Shows which files are loaded and their token cost
- Warns Before Overflow: Alerts at 75%, 90% thresholds
- Suggests Optimization: Recommends
/compactor file removal - Updates Continuously: Refreshes as you work in Kiro
Visual Output Target
┌──────────────────────────────────────────────────────────────────────┐
│ KIRO CONTEXT MONITOR v1.0 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ CONTEXT USAGE: 142,500 / 200,000 tokens (71.3%) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │████████████████████████████████████████████░░░░░░░░░░░░░░░░░░░│ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ BREAKDOWN BY SOURCE │
│ ─────────────────────────────────────────────────────────────── │
│ Chat History ████████████████████████████████████ 102,000 │
│ Context Files ████████████ 35,000 │
│ Steering Rules ████ 4,500 │
│ System/Agent █ 1,000 │
│ │
│ CONTEXT FILES (by token cost) │
│ ─────────────────────────────────────────────────────────────── │
│ src/auth/oauth.ts ████████████ 12,500 tokens │
│ src/auth/session.ts ██████████ 10,200 tokens │
│ src/api/routes.ts ██████ 6,800 tokens │
│ docs/api_spec.md ████ 4,500 tokens │
│ README.md █ 1,000 tokens │
│ │
│ STEERING FILES │
│ ─────────────────────────────────────────────────────────────── │
│ .kiro/steering/tech.md ██ 2,200 tokens │
│ .kiro/steering/security.md █ 1,500 tokens │
│ .kiro/steering/product.md █ 800 tokens │
│ │
│ ⚠ WARNING: Chat history is 72% of total context! │
│ ──────────────────────────────────────────────────────────────── │
│ Recommendations: │
│ • Run /compact to summarize chat history │
│ • Remove unused context files with /context remove │
│ • Consider starting a new session for fresh context │
│ │
│ Press [q] to quit | [r] to refresh | [c] to compact │
└──────────────────────────────────────────────────────────────────────┘
Architecture Overview
┌─────────────────────────────────────────────────────────────────────┐
│ CONTEXT VISUALIZER ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Terminal UI (Ink) │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│ │
│ │ │ Progress Bar │ │ Breakdown Chart │ │ File List ││ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘│ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ Warning Panel ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────┬───────────────────────────┘ │
│ │ │
│ │ State updates │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ State Manager │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ contextState: { ││ │
│ │ │ totalTokens: 142500, ││ │
│ │ │ limit: 200000, ││ │
│ │ │ breakdown: { chat: 102000, files: 35000, ... }, ││ │
│ │ │ files: [ { path, tokens }, ... ], ││ │
│ │ │ warnings: [ ... ] ││ │
│ │ │ } ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────┬───────────────────────────┘ │
│ │ │
│ │ Polling │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Data Fetcher │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Kiro CLI API │ │ Token Counter │ │ │
│ │ │ /context show │ │ (tiktoken) │ │ │
│ │ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ └───────────┼────────────────────┼────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ Kiro CLI │ │ Local Files │ │
│ │ Process │ │ (for estimation) │ │
│ └─────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Expected Deliverables
context-visualizer/
├── src/
│ ├── index.tsx # Entry point
│ ├── App.tsx # Main Ink application
│ ├── components/
│ │ ├── ProgressBar.tsx # Token usage bar
│ │ ├── Breakdown.tsx # Source breakdown
│ │ ├── FileList.tsx # Context files list
│ │ ├── WarningPanel.tsx # Alerts and recommendations
│ │ └── StatusBar.tsx # Controls and help
│ ├── hooks/
│ │ ├── useContextData.ts # Fetch context from Kiro
│ │ ├── useTokenCounter.ts # Estimate tokens locally
│ │ └── usePolling.ts # Periodic refresh
│ ├── utils/
│ │ ├── kiroClient.ts # Kiro CLI interaction
│ │ ├── tokenizer.ts # Token counting
│ │ └── formatters.ts # Display formatting
│ └── types/
│ └── context.ts # TypeScript types
├── package.json
├── tsconfig.json
└── README.md
Solution Architecture
Data Model
// types/context.ts
export interface ContextFile {
path: string;
tokens: number;
addedAt: Date;
category: 'source' | 'doc' | 'config' | 'other';
}
export interface SteeringFile {
path: string;
tokens: number;
scope: 'global' | 'workspace';
inclusion: 'always' | 'agent' | 'never';
}
export interface ContextBreakdown {
systemPrompt: number;
agentConfig: number;
steeringRules: number;
contextFiles: number;
chatHistory: number;
total: number;
}
export interface ContextState {
breakdown: ContextBreakdown;
limit: number;
usagePercent: number;
files: ContextFile[];
steering: SteeringFile[];
lastUpdated: Date;
}
export interface Warning {
level: 'info' | 'warning' | 'critical';
message: string;
recommendation?: string;
}
export interface VisualizerState {
context: ContextState;
warnings: Warning[];
isLoading: boolean;
error?: string;
}
Component Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ INK COMPONENT TREE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ <App> │
│ │ │
│ ├── <Header title="KIRO CONTEXT MONITOR" /> │
│ │ │
│ ├── <ProgressBar │
│ │ current={142500} │
│ │ max={200000} │
│ │ thresholds={[75, 90, 95]} │
│ │ /> │
│ │ │
│ ├── <Breakdown │
│ │ data={{ │
│ │ chatHistory: 102000, │
│ │ contextFiles: 35000, │
│ │ steeringRules: 4500, │
│ │ system: 1000 │
│ │ }} │
│ │ /> │
│ │ │
│ ├── <FileList │
│ │ files={[ │
│ │ { path: 'src/auth/oauth.ts', tokens: 12500 }, │
│ │ { path: 'src/auth/session.ts', tokens: 10200 }, │
│ │ ... │
│ │ ]} │
│ │ onRemove={(path) => removeFile(path)} │
│ │ /> │
│ │ │
│ ├── <WarningPanel warnings={warnings} /> │
│ │ │
│ └── <StatusBar │
│ lastUpdated={lastUpdated} │
│ onRefresh={refresh} │
│ onCompact={compact} │
│ /> │
│ │
└─────────────────────────────────────────────────────────────────────┘
Data Flow
┌─────────────────────────────────────────────────────────────────────┐
│ DATA FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. POLLING TRIGGER (every 5 seconds or manual) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 2. FETCH CONTEXT DATA │ │
│ │ $ kiro-cli /context show --format json │ │
│ │ Response: { tokens: {...}, files: [...], ... } │ │
│ └────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 3. PARSE AND ENRICH │ │
│ │ • Extract token counts │ │
│ │ • Estimate file tokens if not provided │ │
│ │ • Categorize files by type │ │
│ │ • Load steering file metadata │ │
│ └────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 4. GENERATE WARNINGS │ │
│ │ IF usagePercent > 90: │ │
│ │ warnings.push({ level: 'critical', ... }) │ │
│ │ IF chatHistoryPercent > 70: │ │
│ │ warnings.push({ level: 'warning', ... }) │ │
│ └────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 5. UPDATE STATE │ │
│ │ setState({ │ │
│ │ context: newContextState, │ │
│ │ warnings: newWarnings, │ │
│ │ lastUpdated: new Date() │ │
│ │ }) │ │
│ └────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 6. RENDER UI │ │
│ │ Ink re-renders components with new state │ │
│ │ Terminal display updates │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Phased Implementation Guide
Phase 1: Foundation (4-5 hours)
Goal: Set up project structure and basic Kiro CLI integration
What to Build:
- TypeScript + Ink project scaffolding
- Basic Kiro CLI client
- Simple token usage display
Hint 1: Initialize with Ink’s create-ink-app or manually:
mkdir context-visualizer && cd context-visualizer
npm init -y
npm install ink react @types/react typescript tsx
npm install -D @types/node
Hint 2: Create a minimal Kiro client:
// utils/kiroClient.ts
import { execSync } from 'child_process';
export function getContextInfo(): object {
try {
const output = execSync('kiro-cli /context show --format json', {
encoding: 'utf8',
timeout: 5000
});
return JSON.parse(output);
} catch (error) {
return { error: 'Failed to get context' };
}
}
Hint 3: Create a basic Ink app:
// src/App.tsx
import React from 'react';
import { Box, Text } from 'ink';
export function App() {
const usage = 142500;
const limit = 200000;
const percent = ((usage / limit) * 100).toFixed(1);
return (
<Box flexDirection="column">
<Text bold>KIRO CONTEXT MONITOR</Text>
<Text>Usage: {usage.toLocaleString()} / {limit.toLocaleString()} ({percent}%)</Text>
</Box>
);
}
Validation Checkpoint: Running npx tsx src/index.tsx shows basic usage stats.
Phase 2: Core Visualization (5-6 hours)
Goal: Build the progress bar and breakdown components
What to Build:
- Animated progress bar with color thresholds
- Breakdown chart showing token sources
- File list component with token counts
Hint 1: Build a progress bar with color coding:
// components/ProgressBar.tsx
import React from 'react';
import { Box, Text } from 'ink';
interface Props {
current: number;
max: number;
width?: number;
}
export function ProgressBar({ current, max, width = 50 }: Props) {
const percent = (current / max) * 100;
const filled = Math.round((percent / 100) * width);
const empty = width - filled;
// Color based on usage
const color = percent > 90 ? 'red' : percent > 75 ? 'yellow' : 'green';
return (
<Box>
<Text>[</Text>
<Text color={color}>{'█'.repeat(filled)}</Text>
<Text color="gray">{'░'.repeat(empty)}</Text>
<Text>]</Text>
<Text> {percent.toFixed(1)}%</Text>
</Box>
);
}
Hint 2: Create a horizontal bar chart for breakdown:
// components/Breakdown.tsx
import React from 'react';
import { Box, Text } from 'ink';
interface BreakdownItem {
label: string;
tokens: number;
color: string;
}
export function Breakdown({ items, total }: { items: BreakdownItem[], total: number }) {
const maxWidth = 40;
return (
<Box flexDirection="column">
{items.map(item => {
const barWidth = Math.round((item.tokens / total) * maxWidth);
return (
<Box key={item.label}>
<Text>{item.label.padEnd(18)}</Text>
<Text color={item.color}>{'█'.repeat(barWidth)}</Text>
<Text> {item.tokens.toLocaleString()}</Text>
</Box>
);
})}
</Box>
);
}
Hint 3: Use Ink’s useInput for keyboard controls:
import { useInput } from 'ink';
function App() {
useInput((input, key) => {
if (input === 'q') process.exit(0);
if (input === 'r') refresh();
if (input === 'c') compact();
});
// ...
}
Validation Checkpoint: Display shows progress bar and breakdown that update with mock data.
Phase 3: Real-time Updates and Warnings (5-6 hours)
Goal: Add polling, warnings, and interactive features
What to Build:
- Polling hook for periodic updates
- Warning generation logic
- Interactive file removal
- Compact command trigger
Hint 1: Create a polling hook:
// hooks/usePolling.ts
import { useState, useEffect, useCallback } from 'react';
export function usePolling<T>(
fetcher: () => Promise<T>,
intervalMs: number = 5000
) {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(true);
const refresh = useCallback(async () => {
try {
setIsLoading(true);
const result = await fetcher();
setData(result);
setError(null);
} catch (e) {
setError(e as Error);
} finally {
setIsLoading(false);
}
}, [fetcher]);
useEffect(() => {
refresh();
const interval = setInterval(refresh, intervalMs);
return () => clearInterval(interval);
}, [refresh, intervalMs]);
return { data, error, isLoading, refresh };
}
Hint 2: Generate contextual warnings:
// utils/warnings.ts
import { ContextState, Warning } from '../types/context';
export function generateWarnings(state: ContextState): Warning[] {
const warnings: Warning[] = [];
// Critical: approaching limit
if (state.usagePercent > 95) {
warnings.push({
level: 'critical',
message: 'Context window nearly full! Oldest content may be dropped.',
recommendation: 'Run /compact immediately or start a new session.'
});
} else if (state.usagePercent > 90) {
warnings.push({
level: 'critical',
message: `Context usage at ${state.usagePercent.toFixed(1)}%`,
recommendation: 'Consider running /compact to free space.'
});
}
// Warning: chat history dominates
const chatPercent = (state.breakdown.chatHistory / state.breakdown.total) * 100;
if (chatPercent > 70) {
warnings.push({
level: 'warning',
message: `Chat history is ${chatPercent.toFixed(0)}% of context`,
recommendation: 'Run /compact to summarize old messages.'
});
}
// Info: large files
const largeFiles = state.files.filter(f => f.tokens > 10000);
if (largeFiles.length > 0) {
warnings.push({
level: 'info',
message: `${largeFiles.length} file(s) exceed 10K tokens`,
recommendation: 'Consider loading only relevant sections.'
});
}
return warnings;
}
Hint 3: Use tiktoken for accurate token counting:
// utils/tokenizer.ts
import { encoding_for_model } from 'tiktoken';
const encoder = encoding_for_model('cl100k_base'); // Claude's tokenizer
export function countTokens(text: string): number {
return encoder.encode(text).length;
}
export async function countFileTokens(path: string): Promise<number> {
const content = await fs.readFile(path, 'utf8');
return countTokens(content);
}
Validation Checkpoint: Visualizer updates every 5 seconds and shows relevant warnings.
Testing Strategy
Unit Tests
// tests/warnings.test.ts
import { generateWarnings } from '../src/utils/warnings';
describe('generateWarnings', () => {
it('generates critical warning above 90% usage', () => {
const state = {
usagePercent: 92,
breakdown: { total: 184000, chatHistory: 100000 },
// ...
};
const warnings = generateWarnings(state);
expect(warnings.some(w => w.level === 'critical')).toBe(true);
});
it('warns when chat history dominates', () => {
const state = {
usagePercent: 50,
breakdown: { total: 100000, chatHistory: 80000 },
// ...
};
const warnings = generateWarnings(state);
expect(warnings.some(w =>
w.level === 'warning' && w.message.includes('Chat history')
)).toBe(true);
});
});
Integration Tests
// tests/integration.test.ts
import { render } from 'ink-testing-library';
import { App } from '../src/App';
describe('App integration', () => {
it('displays usage information', () => {
const { lastFrame } = render(<App />);
expect(lastFrame()).toContain('CONTEXT MONITOR');
expect(lastFrame()).toMatch(/\d+.*\/.*\d+.*tokens/);
});
it('shows warning panel when usage is high', async () => {
// Mock high usage scenario
jest.spyOn(kiroClient, 'getContextInfo').mockResolvedValue({
tokens: { used: 190000, limit: 200000 }
});
const { lastFrame } = render(<App />);
await waitFor(() => {
expect(lastFrame()).toContain('WARNING');
});
});
});
Manual Testing Checklist
- Start visualizer with no Kiro session (should show error gracefully)
- Start visualizer with active session (should show real data)
- Add files with
/context add(should update in visualizer) - Press ‘r’ to refresh (should update immediately)
- Press ‘q’ to quit (should exit cleanly)
- Reach 90% usage (should show warning)
- Verify token counts match Kiro’s
/context show
Common Pitfalls and Debugging
Pitfall 1: Kiro CLI Not Responding
Symptom: Visualizer hangs or shows stale data
Cause: No active Kiro session or CLI timeout
Debug:
# Check if Kiro is running
pgrep -f "kiro-cli"
# Test CLI manually
kiro-cli /context show --format json
Solution: Add timeout and fallback:
const output = execSync('kiro-cli /context show --format json', {
timeout: 5000, // 5 second timeout
encoding: 'utf8'
});
Pitfall 2: Token Count Mismatch
Symptom: Your counts don’t match Kiro’s display
Cause: Different tokenizer or encoding
Debug:
// Compare your count with Kiro's
const myCount = countTokens(content);
const kiroCount = getContextInfo().tokens.files[path];
console.log(`My: ${myCount}, Kiro: ${kiroCount}, Diff: ${myCount - kiroCount}`);
Solution: Use the correct tokenizer for Claude models:
import { encoding_for_model } from 'tiktoken';
// Claude uses cl100k_base encoding
const encoder = encoding_for_model('cl100k_base');
Pitfall 3: Terminal Rendering Issues
Symptom: UI is garbled or doesn’t fit
Cause: Terminal size too small or encoding issues
Debug:
# Check terminal size
echo "Columns: $COLUMNS, Lines: $LINES"
Solution: Detect terminal size and adapt:
import { useStdout } from 'ink';
function App() {
const { stdout } = useStdout();
const width = stdout?.columns || 80;
// Adjust bar width based on terminal
const barWidth = Math.max(20, Math.min(width - 30, 60));
}
Pitfall 4: Memory Leak from Polling
Symptom: Application slows down over time
Cause: Interval not cleaned up properly
Debug:
// Log each poll
console.log(`[${new Date().toISOString()}] Polling...`);
Solution: Ensure cleanup in useEffect:
useEffect(() => {
const interval = setInterval(refresh, 5000);
return () => {
clearInterval(interval); // Critical!
};
}, []);
Extensions and Challenges
Extension 1: Historical Tracking
Track context usage over time and generate graphs:
interface ContextSnapshot {
timestamp: Date;
usage: number;
breakdown: ContextBreakdown;
}
// Store snapshots in SQLite or JSON
function recordSnapshot(state: ContextState) {
const snapshot = { timestamp: new Date(), ...state };
appendToHistory(snapshot);
}
// Generate sparkline in terminal
function renderSparkline(history: ContextSnapshot[], width: number): string {
// ...
}
Extension 2: Predictive Warnings
Predict when you’ll hit context limit based on growth rate:
function predictOverflow(history: ContextSnapshot[]): Date | null {
// Calculate growth rate
const recent = history.slice(-10);
const growthPerMinute = calculateGrowthRate(recent);
if (growthPerMinute <= 0) return null;
const remaining = limit - current;
const minutesToOverflow = remaining / growthPerMinute;
return new Date(Date.now() + minutesToOverflow * 60 * 1000);
}
Extension 3: Smart Compaction Suggestions
Analyze what would be safe to compact:
function suggestCompaction(state: ContextState): CompactionSuggestion {
// Identify old, unreferenced chat segments
const oldSegments = identifyOldChatSegments(state.chatHistory);
// Estimate savings
const potentialSavings = oldSegments.reduce((sum, s) => sum + s.tokens, 0);
return {
segments: oldSegments,
estimatedSavings: potentialSavings,
safetyLevel: 'safe' // or 'moderate' or 'risky'
};
}
Extension 4: Multi-Session View
Monitor multiple Kiro sessions simultaneously:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ auth-refactor │ │ api-design │ │ bug-fix │
│ ██████████░░ 65%│ │ ████░░░░░░ 38% │ │ ████████░░ 82% │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Challenge: Token Budget Planner
Create a planning mode that helps allocate context before starting work:
Token Budget Planner
────────────────────
Available: 200,000 tokens
Planned allocation:
[ ] Core files (src/auth/*) ~15,000
[ ] Documentation (docs/api.md) ~8,000
[ ] Steering rules ~3,000
[ ] Working room for chat ~174,000
Commit plan? [y/n]
Real-World Connections
How Professionals Use This
- Long-Running Projects: Teams monitor context to avoid losing important early decisions
- Code Review Sessions: Reviewers track which files are loaded for comprehensive reviews
- Debugging Sessions: Engineers ensure error context isn’t pushed out by new content
- Documentation Work: Writers monitor that relevant docs stay in context
Industry Patterns
Resource Monitoring (DevOps): This mirrors CPU/memory monitoring dashboards. The patterns of threshold alerts and resource attribution are universal.
Capacity Planning (SRE): Predicting context overflow is similar to predicting disk space exhaustion or memory pressure.
Cost Visibility (FinOps): Token breakdown is analogous to cloud cost allocation by service/team.
Self-Assessment Checklist
Understanding Verification
- Can you explain why tokens are “expensive” differently for different content?
- Special characters, non-ASCII, and formatting add extra tokens
- Code is often more token-dense than prose
- JSON formatting significantly affects token count
- What happens when context overflows?
- Oldest chat history is silently dropped
- AI loses access to early context without warning
- Files and steering rules are not dropped (they’re “pinned”)
- When should you use
/compactvs. starting fresh?/compact: Preserve important context, summarize history- Fresh session: Major topic change, “clean slate” needed
- How do steering rules affect your context budget?
- Always loaded, consume tokens before you add anything
- More steering = less room for files and chat
- Balance thoroughness with budget
Skill Demonstration
- I can build a terminal UI with Ink
- I can accurately count tokens for any content
- I can integrate with Kiro CLI programmatically
- I can generate actionable warnings based on usage patterns
- I can explain context management to others
Interview Preparation
Be ready to answer:
- “How would you design context management for an AI application?”
- “What strategies exist for working with limited context windows?”
- “How do you prioritize what goes into context vs. what’s retrieved dynamically?”
- “What’s the relationship between context size and inference cost?”
Recommended Reading
| Topic | Resource | Why It Helps |
|---|---|---|
| Tokenization | OpenAI Tokenizer docs | Understand how text becomes tokens |
| Context Windows | “AI Engineering” Ch. 3-4 | Deep dive on context management |
| React in Terminal | Ink documentation | Building TUIs with React |
| Real-time Systems | “Designing Data-Intensive Applications” Ch. 11 | Patterns for streaming data |
| Observability | “Observability Engineering” by Majors et al. | Dashboard design principles |
What Success Looks Like
When you complete this project, you will have:
- A Working Tool: Real-time context monitor running in your terminal
- Deep Token Intuition: Understanding of what consumes context and why
- TUI Development Skills: Ability to build interactive terminal applications
- Proactive Mindset: Monitoring before problems occur
- Foundation for Optimization: Data to drive context management decisions
Next Steps: Move to Project 4 (Custom Agent Factory) to learn how to create specialized agents with scoped permissions.