「AI学习笔记」Harness Engineering: 从 Claude Code 源码看 Harness Engineering 最佳实践

📋 目录

  1. [什么是 Harness Engineering](#什么是 Harness Engineering "#%E4%BB%80%E4%B9%88%E6%98%AF-harness-engineering")
  2. 核心理念:驾驭而非操控
  3. 四大支柱
    • [工具系统 (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")
  4. [Claude Code 的架构设计](#Claude Code 的架构设计 "#claude-code-%E7%9A%84%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1")
  5. 最佳实践与设计模式
  6. 工程权衡与思考
  7. 总结

什么是 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                          │
│  • 代码执行                          │
└─────────────────────────────────────┘

关键原则

  1. 工具即边界:AI 只能通过明确定义的工具与外界交互
  2. 权限即控制:每个操作都经过权限检查,用户保持最终控制权
  3. 上下文即智能:精心管理的上下文让 AI 做出更好的决策
  4. 记忆即延续:跨会话的记忆让 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
}

压缩策略

  1. 保留近期完整历史
typescript 复制代码
const recentMessages = messages.slice(-RECENT_TURN_COUNT)
// 最近 20 轮保持完整,确保短期记忆清晰
  1. 压缩远期历史
typescript 复制代码
const oldMessages = messages.slice(0, -RECENT_TURN_COUNT)
const summary = await generateSummary(oldMessages)

// 摘要包含:
// - 关键文件的修改
// - 重要的决策和讨论
// - 错误和解决方案
// - 工具调用统计
  1. 插入压缩边界
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. 性能优化

关键技术

  1. 并行预取
typescript 复制代码
// 启动时并行加载多个资源
Promise.all([
  loadConfig(),
  loadMemory(),
  connectMCP(),
  prefetchAPIKey(),
])
  1. 惰性加载
typescript 复制代码
// 只在需要时加载重型模块
const { telemetry } = await import('./telemetry.js')
  1. 缓存系统提示
typescript 复制代码
// 系统提示很少变化,可以缓存
const cachedSystemPrompt = memoize(buildSystemPrompt, { ttl: 3600 })

总结

Harness Engineering 是构建生产级 AI Agent 的关键范式。Claude Code 源码为我们展示了:

核心要素

  1. 工具系统:统一接口、强类型、丰富的元数据
  2. 权限模型:多层检查、灵活模式、AI 辅助决策
  3. 上下文管理:分层记忆、智能压缩、缓存优化
  4. 记忆机制:文件化存储、类型化组织、AI 自管理

设计原则

  • 安全第一:默认拒绝,逐步信任
  • 用户掌控:AI 建议,用户决策
  • 可观测性:充分的日志和诊断
  • 渐进增强:从简单到复杂,从手动到自动

实践建议

如果你正在构建自己的 AI Agent 系统:

  1. 从工具接口开始:定义清晰的 Tool 类型
  2. 尽早引入权限:不要等到出问题才加
  3. 重视上下文:这是 AI 智能的基础
  4. 让记忆可见:文件化存储比数据库更透明
相关推荐
kfaino8 小时前
码农的AI翻身(五)你好,我叫 Transformer
后端·aigc
Jackson__11 小时前
做了一段时间的AI coding后,我终于搞清了 CLI 和 MCP 的区别
前端·agent·ai编程
狼爷19 小时前
百年工业史启示:为什么AI落地普遍无效?读懂保罗·戴维的「天轴陷阱」
aigc
aqi0021 小时前
15天学会AI应用开发(十)把文本嵌入模型换成国产模型
人工智能·python·ai编程
用户47949283569151 天前
翻完 lark-cli 的 17 万行 Go 代码,我学到了什么
后端·openai
唐老板1 天前
A2A协议实战:两个Agent怎么聊
ai编程
刘棕霆1 天前
22—AI Skill 测评中断后怎么续跑:active-pipeline.json 断点恢复设计
aigc·ai编程·测试