📋 目录
- [什么是 Harness Engineering](#什么是 Harness Engineering "#%E4%BB%80%E4%B9%88%E6%98%AF-harness-engineering")
- 核心理念:驾驭而非操控
- 四大支柱
- [工具系统 (Tools)](#工具系统 (Tools) "#1-%E5%B7%A5%E5%85%B7%E7%B3%BB%E7%BB%9F-tools")
- [权限与安全 (Permissions)](#权限与安全 (Permissions) "#2-%E6%9D%83%E9%99%90%E4%B8%8E%E5%AE%89%E5%85%A8-permissions")
- [上下文管理 (Context)](#上下文管理 (Context) "#3-%E4%B8%8A%E4%B8%8B%E6%96%87%E7%AE%A1%E7%90%86-context")
- [记忆系统 (Memory)](#记忆系统 (Memory) "#4-%E8%AE%B0%E5%BF%86%E7%B3%BB%E7%BB%9F-memory")
- [Claude Code 的架构设计](#Claude Code 的架构设计 "#claude-code-%E7%9A%84%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1")
- 最佳实践与设计模式
- 工程权衡与思考
- 总结
什么是 Harness Engineering
Harness Engineering 是一种专门用于构建可靠、可控的 AI Agent 系统的软件工程范式。
Harness 一词源自马具(horse harness),隐喻着"引导强大力量朝正确方向前进"。
在 AI Agent 的语境下,Harness Engineering 指的是:
通过精心设计的工具接口、权限模型、上下文管理和记忆机制,将大语言模型的能力引导到实际可用的软件系统中,同时确保安全、可控和可观测。
这不是简单的 LLM 集成,而是一门关于如何让 AI 在真实世界中可靠工作的工程学科。
为什么需要 Harness Engineering?
传统的 LLM 应用(如聊天机器人)主要处理文本输入输出,而 AI Agent 需要:
- 执行真实操作:读写文件、运行命令、调用 API
- 管理复杂状态:跨多轮对话维护上下文和记忆
- 确保安全边界:防止意外破坏或越权操作
- 处理不确定性:优雅地处理错误和边缘情况
- 可观测与调试:提供充分的日志和诊断信息
Claude Code 作为 Anthropic 官方的 CLI 工具,是 Harness Engineering 的典范实现,拥有:
- ~40 个精心设计的工具(Tools)
- 多层次的权限系统(Permissions)
- 智能的上下文压缩(Context Compression)
- 持久化的记忆机制(Memory System)
核心理念:驾驭而非操控
Harness Engineering 的核心是信任但验证(Trust, but Verify):
scss
┌─────────────┐
│ 用户意图 │
└──────┬──────┘
│
▼
┌─────────────────────────────────────┐
│ LLM (Claude) │
│ - 理解意图 │
│ - 规划步骤 │
│ - 选择工具 │
└──────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Harness Layer │
│ ✓ 工具接口 (Tools) │
│ ✓ 权限检查 (Permissions) │
│ ✓ 上下文管理 (Context) │
│ ✓ 记忆存储 (Memory) │
└──────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 操作系统 │
│ • 文件系统 │
│ • Shell 命令 │
│ • 外部 API │
│ • 代码执行 │
└─────────────────────────────────────┘
关键原则:
- 工具即边界:AI 只能通过明确定义的工具与外界交互
- 权限即控制:每个操作都经过权限检查,用户保持最终控制权
- 上下文即智能:精心管理的上下文让 AI 做出更好的决策
- 记忆即延续:跨会话的记忆让 AI 能够持续学习和改进
四大支柱
1. 工具系统 (Tools)
核心架构
在 Claude Code 中,每个工具都是一个自包含的模块,遵循统一的接口设计:
typescript
// src/Tool.ts (简化版)
export type Tool<Input, Output, Progress> = {
// 基本属性
name: string
description: () => Promise<string>
inputSchema: ZodSchema<Input>
outputSchema: ZodSchema<Output>
// 核心方法
call(args: Input, context: ToolUseContext): Promise<ToolResult<Output>>
checkPermissions(input: Input, context: ToolUseContext): Promise<PermissionResult>
// 验证与匹配
validateInput(input: Input, context: ToolUseContext): Promise<ValidationResult>
preparePermissionMatcher(input: Input): Promise<(pattern: string) => boolean>
// 安全属性
isConcurrencySafe(input: Input): boolean
isReadOnly(input: Input): boolean
isDestructive(input: Input): boolean
isOpenWorld(input: Input): boolean
// UI 渲染
renderToolUseMessage(input: Input, options): React.ReactNode
renderToolResultMessage(output: Output, options): React.ReactNode
renderToolUseProgressMessage(progress: Progress[], options): React.ReactNode
// 自动化与分类
toAutoClassifierInput(input: Input): unknown
getActivityDescription(input: Input): string | null
}
工具分类
Claude Code 的 ~40 个工具按功能分为几大类:
1. 文件 I/O 工具
typescript
// src/tools/FileReadTool/FileReadTool.ts
export const FileReadTool = buildTool({
name: 'Read',
inputSchema: z.object({
file_path: z.string(),
start_line: z.number().optional(),
end_line: z.number().optional(),
}),
async call({ file_path, start_line, end_line }, context) {
// 1. 权限检查已在 checkPermissions 完成
// 2. 读取文件内容
const content = await readFileContent(file_path)
// 3. 应用行范围过滤
if (start_line || end_line) {
return sliceLines(content, start_line, end_line)
}
return { data: content }
},
async checkPermissions(input, context) {
// 读取权限检查:是否在工作目录内?
return checkReadPermissionForTool(
input.file_path,
context.getAppState().toolPermissionContext
)
},
isReadOnly: () => true,
isDestructive: () => false,
isConcurrencySafe: () => true, // 读操作可并发
})
2. 搜索工具
typescript
// src/tools/GrepTool/GrepTool.ts
export const GrepTool = buildTool({
name: 'Grep',
inputSchema: z.object({
pattern: z.string(),
path: z.string(),
glob: z.string().optional(),
case_sensitive: z.boolean().optional(),
}),
async call({ pattern, path, glob }, context) {
// 使用 ripgrep 进行高性能搜索
const results = await ripgrepSearch({
pattern,
path,
glob,
maxResults: 100, // 防止结果过大
})
return { data: results }
},
isSearchOrReadCommand: () => ({ isSearch: true, isRead: false }),
})
3. 执行工具
typescript
// src/tools/BashTool/BashTool.ts
export const BashTool = buildTool({
name: 'Bash',
inputSchema: z.object({
command: z.string(),
background: z.boolean().optional(),
}),
async checkPermissions(input, context) {
// 复杂的命令安全检查
const { command } = input
// 1. 检查是否有明确的拒绝规则
if (matchesDenyPattern(command, context.alwaysDenyRules)) {
return { behavior: 'deny', reason: 'Blocked by deny rule' }
}
// 2. 检查是否有破坏性操作
if (isDestructiveCommand(command)) {
return { behavior: 'ask', updatedInput: input }
}
// 3. 在自动模式下,使用分类器评估安全性
if (context.mode === 'auto') {
const safetyScore = await classifyCommandSafety(command)
if (safetyScore < SAFETY_THRESHOLD) {
return { behavior: 'ask', updatedInput: input }
}
}
return { behavior: 'allow', updatedInput: input }
},
async call({ command }, context, canUseTool, parentMessage, onProgress) {
// 执行命令并实时流式返回输出
const process = spawn(command, { shell: true })
process.stdout.on('data', (data) => {
onProgress?.({
toolUseID: parentMessage.id,
data: { type: 'bash_progress', stdout: data.toString() }
})
})
const result = await waitForProcess(process)
return { data: result }
},
isDestructive: (input) => isDestructiveCommand(input.command),
isConcurrencySafe: () => false, // 命令执行不应并发
})
工具设计的最佳实践
1. 使用 Zod 进行严格的输入验证
typescript
// 强类型 + 运行时验证
const inputSchema = z.object({
file_path: z.string().min(1).max(4096),
content: z.string().max(1_000_000), // 防止过大内容
})
2. 分层的权限检查
typescript
// Tool.checkPermissions() - 工具级检查
// → permissions.ts - 系统级检查
// → classifier - AI 分类器检查(auto 模式)
3. 优雅的错误处理
typescript
async call(input, context) {
try {
return { data: await performOperation(input) }
} catch (error) {
if (error instanceof FileNotFoundError) {
// 返回结构化错误,而不是抛出异常
return { data: { error: 'File not found', file: input.file_path } }
}
throw error // 只抛出真正意外的错误
}
}
4. 进度流式更新
typescript
// 长时间运行的操作提供实时反馈
for await (const chunk of processLargeFile(input.file_path)) {
onProgress?.({
toolUseID,
data: { type: 'processing', progress: chunk.progress }
})
}
2. 权限与安全 (Permissions)
权限系统是 Harness Engineering 的核心,确保 AI 的每个操作都在用户控制之下。
权限模式 (Permission Modes)
Claude Code 支持多种权限模式,适应不同的使用场景:
typescript
// src/types/permissions.ts
export type PermissionMode =
| 'default' // 默认:写操作需要确认
| 'plan' // 计划模式:AI 先规划,用户审批后执行
| 'bypassPermissions' // 旁路:信任环境,跳过大部分检查
| 'auto' // 自动:使用 AI 分类器自动决策
| 'autoInternal' // 内部自动模式(Anthropic 员工)
权限检查流程
每个工具调用都会经过多层权限检查:
typescript
// src/utils/permissions/permissions.ts (简化版)
async function checkToolPermission(
tool: Tool,
input: unknown,
context: ToolUseContext
): Promise<PermissionResult> {
// 1. 检查明确的拒绝规则
if (matchesAlwaysDenyRules(tool.name, input, context)) {
return { behavior: 'deny', reason: 'Blocked by deny rule' }
}
// 2. 检查明确的允许规则
if (matchesAlwaysAllowRules(tool.name, input, context)) {
return { behavior: 'allow', updatedInput: input }
}
// 3. 检查权限模式
const mode = context.toolPermissionContext.mode
if (mode === 'bypassPermissions') {
// 旁路模式:只检查明确的 ask/deny 规则
if (!hasExplicitRules(tool.name, input, context)) {
return { behavior: 'allow', updatedInput: input }
}
}
// 4. 工具级权限检查
const toolResult = await tool.checkPermissions(input, context)
if (toolResult.behavior === 'deny') {
return toolResult
}
// 5. Auto 模式:使用 AI 分类器
if (mode === 'auto' && !tool.isReadOnly(input)) {
const classification = await classifyToolSafety(tool, input)
if (classification.isApproved) {
return { behavior: 'allow', updatedInput: input }
} else {
return { behavior: 'ask', updatedInput: input }
}
}
// 6. 默认行为:写操作需要用户确认
if (tool.isReadOnly(input)) {
return { behavior: 'allow', updatedInput: input }
} else {
return { behavior: 'ask', updatedInput: input }
}
}
权限规则示例
用户可以配置细粒度的权限规则:
json
// ~/.claude/config.json
{
"toolPermissions": {
"alwaysAllow": [
"Read(*)", // 允许所有读操作
"Bash(git *)", // 允许所有 git 命令
"Bash(npm install)", // 允许 npm install
"Write(*.md)" // 允许写入 Markdown 文件
],
"alwaysDeny": [
"Bash(rm -rf *)", // 拒绝危险的删除操作
"Bash(sudo *)", // 拒绝 sudo 命令
"Write(/etc/*)" // 拒绝修改系统配置
],
"alwaysAsk": [
"Bash(npm publish*)", // 发布操作总是询问
"Write(.env*)" // 修改环境变量文件总是询问
]
}
}
Auto 模式的 AI 分类器
在 auto 模式下,Claude Code 使用一个轻量级的 AI 分类器来自动评估操作的安全性:
typescript
// 分类器输入:工具名 + 参数的紧凑表示
async function classifyToolSafety(tool: Tool, input: unknown): Promise<Classification> {
const classifierInput = tool.toAutoClassifierInput(input)
// 调用专门的分类模型
const response = await callClassifierAPI({
tool: tool.name,
input: classifierInput,
context: {
workingDirectory: getCwd(),
recentActions: getRecentToolCalls(),
}
})
return {
isApproved: response.safetyScore > THRESHOLD,
confidence: response.confidence,
reasoning: response.reasoning,
}
}
这个设计的巧妙之处在于:
- 快速:轻量级模型,延迟低
- 准确:专门训练用于安全评估
- 可解释:提供决策理由
- 可回退:用户始终可以覆盖决策
3. 上下文管理 (Context)
上下文管理是 AI Agent 智能的基础。Claude Code 采用了多层次的上下文压缩策略。
上下文的挑战
大语言模型的上下文窗口是有限的(如 Claude 3.5 Sonnet 的 200K tokens),而一个长时间的编程会话可能产生:
- 数百轮对话
- 数十个文件的读写记录
- 数百条 shell 命令的输出
- 大量的工具调用和结果
如何在有限的窗口内保持最有用的信息?
Snip Compaction(快照压缩)
Claude Code 使用一种称为 "Snip Compaction" 的技术:
typescript
// src/services/compact/snipCompact.ts 的核心思想
interface SnipCompactionStrategy {
// 1. 保留最近的 N 轮完整对话
preservedTurns: number // 如最近 20 轮
// 2. 对更早的历史进行压缩
compactOlderHistory: (messages: Message[]) => CompactedSnapshot
// 3. 在压缩边界插入摘要
insertBoundary: (snapshot: CompactedSnapshot) => SystemMessage
}
压缩策略:
- 保留近期完整历史
typescript
const recentMessages = messages.slice(-RECENT_TURN_COUNT)
// 最近 20 轮保持完整,确保短期记忆清晰
- 压缩远期历史
typescript
const oldMessages = messages.slice(0, -RECENT_TURN_COUNT)
const summary = await generateSummary(oldMessages)
// 摘要包含:
// - 关键文件的修改
// - 重要的决策和讨论
// - 错误和解决方案
// - 工具调用统计
- 插入压缩边界
typescript
const boundaryMessage: SystemMessage = {
type: 'system',
subtype: 'compact_boundary',
content: summary,
metadata: {
compactedMessageCount: oldMessages.length,
preservedSegment: {
tailUuid: recentMessages[0].uuid,
headUuid: recentMessages[recentMessages.length - 1].uuid,
}
}
}
分层记忆架构
java
┌─────────────────────────────────────┐
│ System Prompt (固定) │
│ - 工具定义 │
│ - 行为准则 │
│ - 记忆系统说明 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ User Context (会话开始时) │
│ - 工作目录 │
│ - Git 状态 │
│ - 环境信息 │
│ - MEMORY.md 内容 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Compacted History (压缩的历史) │
│ - 摘要:前 N-20 轮 │
│ - 关键信息提取 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Recent Messages (完整的近期对话) │
│ - 最近 20 轮完整保留 │
│ - 所有工具调用和结果 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Current Turn (当前轮次) │
│ - 用户的新输入 │
│ - 即将生成的响应 │
└─────────────────────────────────────┘
FileStateCache(文件状态缓存)
为了避免重复读取相同文件,Claude Code 维护了一个文件状态缓存:
typescript
// src/utils/fileStateCache.ts 的核心思想
class FileStateCache {
private cache = new Map<string, CachedFileState>()
async get(filePath: string): Promise<FileContent | null> {
const cached = this.cache.get(filePath)
if (cached) {
// 检查文件是否被修改(通过 mtime)
const currentStat = await fs.stat(filePath)
if (currentStat.mtime <= cached.mtime) {
return cached.content // 命中缓存
}
}
// 缓存未命中或已失效
const content = await fs.readFile(filePath)
this.cache.set(filePath, {
content,
mtime: (await fs.stat(filePath)).mtime
})
return content
}
}
这避免了:
- 在同一轮对话中多次读取同一文件
- 将重复的文件内容发送给 LLM
- 浪费宝贵的上下文窗口
4. 记忆系统 (Memory)
记忆系统让 AI Agent 能够跨会话保持状态和学习。
Memory Directory 架构
Claude Code 使用基于文件的记忆系统:
bash
~/.claude/projects/<project-slug>/memory/
├── MEMORY.md # 记忆索引(总是加载)
├── user_preferences.md # 用户偏好
├── project_context.md # 项目背景
├── feedback_patterns.md # 反馈和改进
└── team/ # 团队共享记忆(可选)
├── MEMORY.md
├── team_standards.md
└── shared_context.md
记忆类型分类
Claude Code 定义了四种记忆类型:
markdown
# Memory Types
## 1. User Memory
Who the user is, their role, preferences, and how they like to work.
Examples:
- "User is a senior backend engineer at Stripe"
- "Prefers TypeScript over JavaScript"
- "Dislikes verbose commit messages"
## 2. Feedback Memory
What worked well, what didn't, and corrections.
Examples:
- "User wants diffs shown before applying changes"
- "Avoid summarizing code --- show full implementations"
- "Always run tests after file edits"
## 3. Project Memory
Long-term project context that isn't derivable from code.
Examples:
- "Project deadline: Q2 2024"
- "Architecture decision: using PostgreSQL for persistence"
- "Incident on 2024-01-15: database migration failure"
## 4. Reference Memory
Pointers to external systems and documentation.
记忆的加载和保存
typescript
// src/memdir/memdir.ts (简化版)
// 1. 加载记忆到系统提示中
export async function loadMemoryPrompt(): Promise<string | null> {
const memoryDir = getAutoMemPath()
const entrypointPath = join(memoryDir, 'MEMORY.md')
let content = ''
try {
content = await fs.readFile(entrypointPath, 'utf-8')
} catch {
// 记忆文件不存在
}
const lines = [
'# auto memory',
'',
`You have a persistent memory system at \`${memoryDir}\`.`,
'',
'## How to save memories',
'',
'**Step 1** --- Write the memory to its own file using frontmatter:',
'',
'```markdown',
'---',
'name: User prefers TypeScript',
'type: user',
'description: User has expressed strong preference for TypeScript',
'---',
'',
'User explicitly mentioned: "Always use TypeScript, not JavaScript"',
'```',
'',
'**Step 2** --- Add a pointer in `MEMORY.md`:',
'',
'```markdown',
'- [TypeScript Preference](user_typescript.md) --- User prefers TS over JS',
'```',
'',
'## When to access memories',
'',
'- At session start: Read MEMORY.md for context',
'- When user mentions past work: Search memory files',
'- After corrections: Update relevant memories',
'',
]
if (content.trim()) {
const truncated = truncateEntrypointContent(content)
lines.push('## MEMORY.md', '', truncated.content)
}
return lines.join('\n')
}
MEMORY.md 的截断策略
为了防止记忆索引过大,Claude Code 实施了严格的限制:
typescript
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000
export function truncateEntrypointContent(raw: string): EntrypointTruncation {
const lines = raw.trim().split('\n')
const lineCount = lines.length
const byteCount = raw.length
const wasLineTruncated = lineCount > MAX_ENTRYPOINT_LINES
const wasByteTruncated = byteCount > MAX_ENTRYPOINT_BYTES
if (!wasLineTruncated && !wasByteTruncated) {
return { content: raw, lineCount, byteCount, wasLineTruncated, wasByteTruncated }
}
// 先按行数截断
let truncated = wasLineTruncated
? lines.slice(0, MAX_ENTRYPOINT_LINES).join('\n')
: raw
// 再按字节数截断(在最后一个换行符处)
if (truncated.length > MAX_ENTRYPOINT_BYTES) {
const cutAt = truncated.lastIndexOf('\n', MAX_ENTRYPOINT_BYTES)
truncated = truncated.slice(0, cutAt > 0 ? cutAt : MAX_ENTRYPOINT_BYTES)
}
return {
content: truncated + '\n\n> WARNING: MEMORY.md exceeded limits...',
lineCount,
byteCount,
wasLineTruncated,
wasByteTruncated,
}
}
设计理念:
- MEMORY.md 是索引,不是记忆本身
- 每个条目应该是一行,~150 字符
- 详细内容放在独立的文件中
- 让 AI 自己管理记忆的组织
Claude Code 的架构设计
理解了四大支柱后,让我们看看 Claude Code 是如何将它们整合成一个完整系统的。
核心流程:从用户输入到工具执行
typescript
// src/QueryEngine.ts (简化版)
export class QueryEngine {
async *submitMessage(prompt: string): AsyncGenerator<SDKMessage> {
// 1. 处理用户输入(slash 命令、附件等)
const { messages, shouldQuery, allowedTools } = await processUserInput({
input: prompt,
context: this.context,
})
this.messages.push(...messages)
// 2. 构建系统提示
const systemPrompt = await this.buildSystemPrompt()
// 3. 进入查询循环
for await (const message of query({
messages: this.messages,
systemPrompt,
canUseTool: this.wrappedCanUseTool,
context: this.context,
})) {
if (message.type === 'assistant') {
// 4. 处理助手消息(可能包含工具调用)
yield message
for (const toolUse of message.toolUses) {
// 5. 执行工具(带权限检查)
const result = await this.executeTool(toolUse)
yield result
}
}
}
}
private async executeTool(toolUse: ToolUse): Promise<ToolResult> {
const tool = findToolByName(this.tools, toolUse.name)
// 权限检查
const permission = await checkToolPermission(tool, toolUse.input, this.context)
if (permission.behavior === 'deny') {
return { error: 'Permission denied' }
}
if (permission.behavior === 'ask') {
const userDecision = await this.promptUser(tool, toolUse.input)
if (userDecision !== 'approve') {
return { error: 'User rejected' }
}
}
// 执行工具
return await tool.call(toolUse.input, this.context)
}
}
数据流图
scss
用户输入
│
▼
┌────────────────────────┐
│ processUserInput() │ ← 处理 slash 命令、附件
└──────────┬─────────────┘
│
▼
┌────────────────────────┐
│ buildSystemPrompt() │ ← 加载记忆、工具定义
└──────────┬─────────────┘
│
▼
┌────────────────────────┐
│ query() 循环 │ ← 调用 Claude API
└──────────┬─────────────┘
│
├─→ AssistantMessage
│ │
│ └─→ ToolUse[]
│ │
│ ▼
│ ┌──────────────────┐
│ │ checkPermissions │
│ └────────┬─────────┘
│ │
│ ├─→ allow → tool.call()
│ ├─→ ask → promptUser()
│ └─→ deny → error
│ │
│ ▼
│ ToolResult
│ │
├───────────────────────┘
│
▼
nextMessage
关键组件
1. Tool Registry
typescript
// src/tools.ts
export function buildTools(options: BuildToolsOptions): Tools {
const tools: Tool[] = [
FileReadTool,
FileWriteTool,
FileEditTool,
BashTool,
GrepTool,
GlobTool,
// ... ~40 个工具
]
// 根据环境和配置过滤
return tools.filter(t => t.isEnabled())
}
2. Permission System
typescript
// src/hooks/useCanUseTool.ts
export const useCanUseTool = (): CanUseToolFn => {
return async (tool, input, context, assistantMessage, toolUseID) => {
// 多层检查
const result = await checkToolPermission(tool, input, context)
// 记录决策用于审计
logPermissionDecision({
tool: tool.name,
input,
decision: result.behavior,
timestamp: Date.now(),
})
return result
}
}
3. Context Builder
typescript
// src/utils/queryContext.ts
export async function fetchSystemPromptParts(options) {
const parts = []
// 1. 核心系统提示
parts.push(await loadCoreSystemPrompt())
// 2. 工具定义
parts.push(await buildToolPrompts(options.tools))
// 3. 记忆系统
const memory = await loadMemoryPrompt()
if (memory) parts.push(memory)
// 4. MCP 资源
parts.push(await buildMCPPrompts(options.mcpClients))
return {
defaultSystemPrompt: parts.join('\n\n'),
userContext: await buildUserContext(),
systemContext: await buildSystemContext(),
}
}
最佳实践与设计模式
1. 构建器模式(Builder Pattern)
所有工具使用统一的构建器:
typescript
export const MyTool = buildTool({
name: 'MyTool',
inputSchema: z.object({ ... }),
async call(input, context) { ... },
async checkPermissions(input, context) { ... },
// 可选方法有合理的默认值
isReadOnly: (input) => false,
isDestructive: (input) => false,
isConcurrencySafe: (input) => false,
})
优点:
- 强制接口一致性
- 提供安全的默认值
- 类型安全
- 易于扩展
2. 分层验证(Layered Validation)
typescript
// 第 1 层:Zod Schema(编译时 + 运行时)
const inputSchema = z.object({
file_path: z.string().min(1),
})
// 第 2 层:工具级验证
async validateInput(input, context) {
if (!await fs.exists(input.file_path)) {
return { result: false, message: 'File not found' }
}
return { result: true }
}
// 第 3 层:权限检查
async checkPermissions(input, context) {
if (isOutsideWorkingDirectory(input.file_path)) {
return { behavior: 'ask', updatedInput: input }
}
return { behavior: 'allow', updatedInput: input }
}
3. 流式响应(Streaming)
typescript
async *executeLongRunningTool(input, onProgress) {
for await (const chunk of processInChunks(input)) {
// 实时进度更新
onProgress?.({
toolUseID,
data: {
type: 'progress',
current: chunk.index,
total: chunk.total,
}
})
yield chunk.result
}
}
4. 缓存优先(Cache-First)
typescript
class FileStateCache {
async readFile(path: string): Promise<string> {
// 1. 检查缓存
if (this.cache.has(path)) {
const cached = this.cache.get(path)
if (!await this.isStale(cached)) {
return cached.content
}
}
// 2. 读取文件
const content = await fs.readFile(path)
// 3. 更新缓存
this.cache.set(path, { content, mtime: Date.now() })
return content
}
}
5. 错误即数据(Errors as Data)
typescript
// 不好:抛出异常
async function readFile(path: string): Promise<string> {
if (!await fs.exists(path)) {
throw new Error('File not found')
}
return await fs.readFile(path)
}
// 好:返回 Result 类型
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }
async function readFile(path: string): Promise<Result<string, string>> {
if (!await fs.exists(path)) {
return { ok: false, error: 'File not found' }
}
const content = await fs.readFile(path)
return { ok: true, value: content }
}
工程权衡与思考
1. 安全 vs 便利
问题:严格的权限检查会影响用户体验,但松散的检查有风险。
Claude Code 的解决方案:
- 提供多种权限模式(default, auto, bypassPermissions)
- 用户可以针对特定操作配置规则
- Auto 模式使用 AI 分类器在两者之间取得平衡
关键启示:
不要一刀切。给用户选择权,同时提供智能默认值。
2. 上下文窗口管理
问题:如何在有限的上下文窗口中保持足够的信息?
Claude Code 的解决方案:
- 分层记忆:System Prompt → Compacted History → Recent Messages
- 文件状态缓存:避免重复内容
- 智能摘要:压缩旧历史但保留关键信息
关键启示:
上下文管理不是简单的截断,而是智能的信息保留。
3. 工具粒度
问题:工具应该是粗粒度(如"EditProject")还是细粒度(如"ReplaceString")?
Claude Code 的选择:
- 大部分是细粒度工具(Read, Write, Edit, Grep)
- 少数高层工具(如 AgentTool 用于子代理)
- 用户可以通过 Skills 定义自己的复合操作
关键启示:
细粒度工具更灵活,组合性更强。让 AI 学习如何组合它们。
4. 性能优化
关键技术:
- 并行预取
typescript
// 启动时并行加载多个资源
Promise.all([
loadConfig(),
loadMemory(),
connectMCP(),
prefetchAPIKey(),
])
- 惰性加载
typescript
// 只在需要时加载重型模块
const { telemetry } = await import('./telemetry.js')
- 缓存系统提示
typescript
// 系统提示很少变化,可以缓存
const cachedSystemPrompt = memoize(buildSystemPrompt, { ttl: 3600 })
总结
Harness Engineering 是构建生产级 AI Agent 的关键范式。Claude Code 源码为我们展示了:
核心要素
- 工具系统:统一接口、强类型、丰富的元数据
- 权限模型:多层检查、灵活模式、AI 辅助决策
- 上下文管理:分层记忆、智能压缩、缓存优化
- 记忆机制:文件化存储、类型化组织、AI 自管理
设计原则
- 安全第一:默认拒绝,逐步信任
- 用户掌控:AI 建议,用户决策
- 可观测性:充分的日志和诊断
- 渐进增强:从简单到复杂,从手动到自动
实践建议
如果你正在构建自己的 AI Agent 系统:
- 从工具接口开始:定义清晰的 Tool 类型
- 尽早引入权限:不要等到出问题才加
- 重视上下文:这是 AI 智能的基础
- 让记忆可见:文件化存储比数据库更透明