- name
- claude-code-agent-harness
- description
- Deep architectural knowledge of AI Agent Harness design patterns, implementation strategies, and Claude Code internals for building production-grade AI agents
- triggers
- ["how do I build an agent harness","explain the agent conversation loop","implement tool system for my agent","set up agent permission pipeline","design agent context management","create MCP integration for agent","build sub-agent fork mechanism","implement agent memory system"]
# Claude Code Agent Harness Architecture
> Skill by [ara.so](https://ara.so) — Claude Code Skills collection.
## Overview
This project is a comprehensive 420,000-word architectural analysis of AI Agent Harness design, using Claude Code as the reference implementation. It provides production-ready patterns for building agent systems with conversation loops, tool execution, permission management, context compression, memory systems, and multi-agent orchestration.
**Key Capabilities:**
- Agent conversation loop architecture (async generator pattern)
- Tool system design with 50+ tool implementations
- Four-stage permission pipeline
- Context management and compression strategies
- Memory systems and fork mechanisms
- MCP (Model Context Protocol) integration
- Sub-agent orchestration patterns
- Streaming architecture and performance optimization
## Installation
```bash
# Clone the reference architecture
git clone https://github.com/lintsinghua/claude-code-book.git
cd claude-code-book
# Read online (recommended for interactive diagrams)
# Visit: https://lintsinghua.github.io
```
## Core Architecture Patterns
### 1. Conversation Loop (Agent Heartbeat)
The conversation loop is an async generator that drives agent execution:
```typescript
// Core conversation loop pattern
async function* conversationLoop(
deps: QueryDeps
): AsyncGenerator<YieldEvent, void, void> {
while (true) {
// 1. Generate LLM response
const response = await deps.llm.generate({
messages: deps.context.messages,
tools: deps.tools.available,
});
// 2. Yield streaming events
for (const chunk of response.stream) {
yield { type: 'text_delta', delta: chunk };
}
// 3. Handle tool calls
if (response.toolCalls) {
for (const call of response.toolCalls) {
const result = await deps.tools.execute(call);
yield { type: 'tool_result', result };
deps.context.addMessage({ role: 'tool', content: result });
}
continue; // Loop back for next turn
}
// 4. Check termination conditions
if (shouldTerminate(response, deps)) {
yield { type: 'done', reason: getTerminationReason(response) };
break;
}
}
}
// Five yield event types
type YieldEvent =
| { type: 'text_delta'; delta: string }
| { type: 'tool_call'; call: ToolCall }
| { type: 'tool_result'; result: ToolResult }
| { type: 'thinking'; content: string }
| { type: 'done'; reason: TerminationReason };
// Ten termination reasons
type TerminationReason =
| 'max_turns'
| 'user_interrupt'
| 'explicit_stop'
| 'error'
| 'context_overflow'
| 'permission_denied'
| 'natural_completion'
| 'timeout'
| 'sub_agent_complete'
| 'plan_complete';
```
### 2. Tool System (Agent's Hands)
Tools follow a five-element protocol:
```typescript
// Tool definition interface
interface Tool<TInput, TOutput, TParams> {
name: string;
description: string;
inputSchema: z.ZodSchema<TInput>;
execute: (input: TInput, params: TParams) => Promise<TOutput>;
metadata: ToolMetadata;
}
interface ToolMetadata {
readOnly: boolean;
destructive: boolean;
concurrencySafe: boolean;
requiresConfirmation: boolean;
category: ToolCategory;
}
// Fault-safe tool builder
function buildTool<TInput, TOutput>(
config: ToolConfig<TInput, TOutput>
): Tool<TInput, TOutput> {
return {
name: config.name,
description: config.description,
inputSchema: config.schema,
execute: async (input, params) => {
try {
// Validate input
const validated = config.schema.parse(input);
// Check permissions
if (config.requiresConfirmation && !params.autoApprove) {
const approved = await params.permissions.requestApproval({
tool: config.name,
input: validated,
});
if (!approved) throw new PermissionDeniedError();
}
// Execute with timeout
return await Promise.race([
config.handler(validated, params),
timeout(params.timeout || 30000),
]);
} catch (error) {
return handleToolError(error, config.name);
}
},
metadata: config.metadata,
};
}
// Example tool: file reader
const readFileTool = buildTool({
name: 'read_file',
description: 'Read content from a file',
schema: z.object({
path: z.string(),
encoding: z.enum(['utf8', 'base64']).default('utf8'),
}),
metadata: {
readOnly: true,
destructive: false,
concurrencySafe: true,
requiresConfirmation: false,
category: 'filesystem',
},
handler: async (input, params) => {
const fs = await import('fs/promises');
const content = await fs.readFile(input.path, input.encoding);
return { content, size: content.length };
},
});
```
### 3. Permission Pipeline (Agent's Guardrails)
Four-stage permission management:
```typescript
// Permission modes spectrum
type PermissionMode =
| 'autonomous' // Auto-approve all
| 'interactive' // Prompt for destructive
| 'strict' // Prompt for all
| 'read_only' // Block destructive
| 'sandbox'; // Isolated environment
interface PermissionPipeline {
// Stage 1: Static rule matching
checkRules(tool: ToolCall): Promise<RuleDecision>;
// Stage 2: Speculative classification
classify(tool: ToolCall): Promise<RiskLevel>;
// Stage 3: User approval (if needed)
requestApproval(tool: ToolCall): Promise<boolean>;
// Stage 4: Audit logging
logExecution(tool: ToolCall, result: ToolResult): Promise<void>;
}
class FourStagePermissionPipeline implements PermissionPipeline {
async checkRules(tool: ToolCall): Promise<RuleDecision> {
// Bash pattern matching for file paths
for (const rule of this.rules) {
if (matchPattern(rule.pattern, tool.input)) {
return rule.decision;
}
}
return { decision: 'defer', reason: 'no_match' };
}
async classify(tool: ToolCall): Promise<RiskLevel> {
// 2-second Promise.race with LLM classifier
const classification = await Promise.race([
this.llmClassifier.classify(tool),
timeout(2000, { risk: 'medium' }),
]);
return classification.risk;
}
async requestApproval(tool: ToolCall): Promise<boolean> {
if (this.mode === 'autonomous') return true;
if (this.mode === 'read_only' && tool.metadata.destructive) {
return false;
}
return await this.ui.prompt({
message: `Allow ${tool.name}?`,
details: tool.input,
risk: await this.classify(tool),
});
}
async logExecution(
tool: ToolCall,
result: ToolResult
): Promise<void> {
await this.auditLog.write({
timestamp: Date.now(),
tool: tool.name,
input: tool.input,
output: result,
approved: result.approved,
userId: this.userId,
});
}
}
```
### 4. Context Management (Working Memory)
Progressive compression with circuit breaker:
```typescript
// Effective window formula
interface ContextWindow {
total: number;
reserved: {
system: number;
tools: number;
recent: number;
};
available: number; // total - sum(reserved)
}
// Four-level progressive compression
class ContextManager {
private stages: CompressionStage[] = [
{ name: 'snip', threshold: 0.7, handler: this.snipOldMessages },
{ name: 'micro_compact', threshold: 0.8, handler: this.microCompact },
{ name: 'collapse', threshold: 0.9, handler: this.collapseBlocks },
{ name: 'auto_compact', threshold: 0.95, handler: this.autoCompact },
];
async compress(
messages: Message[],
budget: ContextWindow
): Promise<Message[]> {
const usage = this.calculateUsage(messages);
const ratio = usage / budget.available;
// Apply compression stages progressively
for (const stage of this.stages) {
if (ratio >= stage.threshold) {
messages = await stage.handler(messages, budget);
}
}
// Circuit breaker if still over budget
if (this.calculateUsage(messages) > budget.available) {
throw new ContextOverflowError('Cannot compress within budget');
}
return messages;
}
private async snipOldMessages(
messages: Message[],
budget: ContextWindow
): Promise<Message[]> {
// Keep system + recent + important, snip middle
const recent = messages.slice(-budget.reserved.recent);
const important = messages.filter(m => m.metadata?.important);
const system = messages.filter(m => m.role === 'system');
return [...system, ...important, ...recent];
}
private async microCompact(
messages: Message[]
): Promise<Message[]> {
// Compress tool results to summaries
return messages.map(msg => {
if (msg.role === 'tool' && msg.content.length > 1000) {
return {
...msg,
content: this.summarize(msg.content, 200),
metadata: { ...msg.metadata, compressed: true },
};
}
return msg;
});
}
}
```
### 5. Memory System (Long-term Memory)
Four closed-form memory types:
```typescript
// Memory types
interface MemorySystem {
facts: Map<string, Fact>; // Immutable truths
preferences: Map<string, any>; // User settings
context: Map<string, Context>; // Session state
learned: Map<string, Learned>; // Accumulated knowledge
}
class AgentMemory {
private index: MemoryIndex; // MEMORY.md file
async save(key: string, value: MemoryEntry): Promise<void> {
// "Only save what cannot be derived"
if (this.isDerived(value)) {
return; // Skip redundant information
}
await this.store.set(key, value);
await this.updateIndex(key, value);
}
async fork(parentMemory: AgentMemory): Promise<AgentMemory> {
// Byte-level context inheritance for sub-agents
const forked = new AgentMemory();
// Copy immutable facts (reference, not clone)
forked.facts = parentMemory.facts;
// Clone mutable state
forked.context = new Map(parentMemory.context);
forked.preferences = new Map(parentMemory.preferences);
// Start fresh learned knowledge
forked.learned = new Map();
return forked;
}
private async updateIndex(
key: string,
value: MemoryEntry
): Promise<void> {
const index = await this.loadIndex();
index.entries[key] = {
type: value.type,
created: value.timestamp,
summary: this.summarize(value),
};
await this.saveIndex(index);
}
}
```
### 6. Sub-Agent Fork Pattern
Recursive agent spawning with inheritance:
```typescript
interface SubAgentConfig {
type: 'custom' | 'builtin';
agentPath?: string;
builtinName?: 'coordinator' | 'specialist' | 'reviewer' | 'planner';
inheritContext: boolean;
inheritMemory: boolean;
inheritTools: boolean;
}
class AgentFork {
async spawn(
parent: Agent,
config: SubAgentConfig
): Promise<Agent> {
// Load agent definition
const agentDef = config.type === 'builtin'
? await this.loadBuiltin(config.builtinName!)
: await this.loadCustom(config.agentPath!);
// Create child with inheritance
const child = new Agent({
...agentDef,
context: config.inheritContext
? parent.context.fork()
: new Context(),
memory: config.inheritMemory
? await parent.memory.fork()
: new AgentMemory(),
tools: config.inheritTools
? [...parent.tools.available]
: agentDef.tools,
});
// Recursive fork prevention
child.metadata.forkDepth = (parent.metadata.forkDepth || 0) + 1;
if (child.metadata.forkDepth > this.maxForkDepth) {
throw new MaxForkDepthError();
}
return child;
}
}
// Coordinator-Worker pattern
async function coordinatorWorkerPattern(
task: Task
): Promise<Result> {
const coordinator = await agentFork.spawn(mainAgent, {
type: 'builtin',
builtinName: 'coordinator',
inheritContext: true,
inheritMemory: false,
inheritTools: false, // Coordinators only orchestrate
});
const plan = await coordinator.plan(task);
const workers = await Promise.all(
plan.subtasks.map(subtask =>
agentFork.spawn(coordinator, {
type: 'builtin',
builtinName: 'specialist',
inheritContext: true,
inheritMemory: true,
inheritTools: true,
})
)
);
const results = await Promise.all(
workers.map((worker, i) => worker.execute(plan.subtasks[i]))
);
return coordinator.synthesize(results);
}
```
### 7. MCP Integration
Model Context Protocol bridge for external tools:
```typescript
// Eight transport protocols
type MCPTransport =
Voir sur GitHub