Agentic Coding Assistant 架构解析

代码生成 Agent 的上下文工程、编辑应用策略、测试驱动循环与自主编程范式

引言

2024-2025 年,Coding Assistant 从"自动补全"进化到"自主编程"。Cursor、GitHub Copilot Workspace、Devin、Claude Code、Windsurf——这些工具不再只是"帮你写下一行",而是能理解整个代码库、规划修改策略、执行多文件编辑、运行测试并自主修复错误。

这个跳跃背后是一套完整的 Agent 架构:上下文工程决定了模型"看到什么",编辑策略决定了修改"怎么落地",测试驱动循环决定了质量"怎么保证"。本文从架构视角拆解 Agentic Coding Assistant 的核心设计。

架构总览

系统分层

┌─────────────────────────────────────────────────────────────────┐
│                         用户界面层                               │
│   IDE Plugin (VS Code/JetBrains)  |  CLI  |  Web Chat          │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                     Agent Orchestrator                           │
│                                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │ 任务规划  │  │ 上下文    │  │ 代码生成  │  │ 验证循环  │       │
│  │ Planner  │  │ Builder  │  │ Generator│  │ Verifier │       │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘       │
│       │              │              │              │             │
│  ┌────▼──────────────▼──────────────▼──────────────▼────┐       │
│  │              Tool Executor (Sandbox)                   │       │
│  │   File R/W  |  Shell  |  LSP  |  Search  |  Git      │       │
│  └──────────────────────────────────────────────────────┘       │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                     代码库知识层                                 │
│                                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │ 代码索引  │  │ 符号图谱  │  │ 文档索引  │  │ Git 历史  │       │
│  │ Embeddings│  │ LSP/AST │  │ Docs RAG │  │ Blame    │       │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘       │
└─────────────────────────────────────────────────────────────────┘

与传统自动补全的对比

维度 自动补全 (Copilot v1) Agentic Assistant
输入 当前文件 + 光标上下文 整个代码库 + 任务描述
输出 下一行/下一段代码 多文件编辑 + 测试 + 文档
决策 单步预测 多步规划 + 工具调用
验证 编译/测试/Lint 自动验证
交互 Tab 接受/拒绝 自然语言对话
上下文窗口 ~6K tokens 128K-200K tokens + RAG
自主性 被动(用户触发) 主动(自主规划执行)

上下文工程

核心挑战

代码库可能有数百万行代码,但模型的上下文窗口是有限的。上下文工程的目标是:用最少的 token 传递最相关的信息。

┌────────────────────────────────────────────────────────────┐
│               上下文预算分配(128K tokens 示例)              │
│                                                            │
│   System Prompt + Rules     ████                   ~5K     │
│   Task Description          ██                     ~2K     │
│   Active File(s)            ████████               ~10K    │
│   Related Files (RAG)       ████████████████       ~20K    │
│   Symbol Definitions        ████████               ~10K    │
│   Test Files                ████████               ~10K    │
│   Documentation             ██████                 ~8K     │
│   Conversation History      ██████████             ~13K    │
│   Tool Results              ████████████████████   ~30K    │
│   Reserved for Generation   ████████████████████   ~20K    │
│                                                            │
│   Total Budget:             ████████████████████   ~128K   │
└────────────────────────────────────────────────────────────┘

上下文构建策略

// src/context/context_builder.ts

interface ContextItem {
  content: string;
  source: string;
  relevance: number;  // 0-1
  tokenCount: number;
  priority: "required" | "high" | "medium" | "low";
}

class ContextBuilder {
  private budget: number;
  private items: ContextItem[] = [];

  constructor(maxTokens: number = 128000) {
    // Reserve 20% for generation
    this.budget = Math.floor(maxTokens * 0.8);
  }

  /**
   * Build context for a coding task.
   * Strategy: required items first, then by relevance score.
   */
  async buildContext(task: CodingTask): Promise<string> {
    // Phase 1: Required context (always included)
    this.addRequired(task);

    // Phase 2: Gather candidates ranked by relevance
    const candidates = await this.gatherCandidates(task);

    // Phase 3: Fill remaining budget by priority + relevance
    this.fillBudget(candidates);

    // Phase 4: Assemble final prompt
    return this.assemble();
  }

  private addRequired(task: CodingTask): void {
    // System prompt
    this.items.push({
      content: SYSTEM_PROMPT,
      source: "system",
      relevance: 1.0,
      tokenCount: countTokens(SYSTEM_PROMPT),
      priority: "required",
    });

    // Task description
    this.items.push({
      content: task.description,
      source: "task",
      relevance: 1.0,
      tokenCount: countTokens(task.description),
      priority: "required",
    });

    // Active file (the file user is editing)
    if (task.activeFile) {
      this.items.push({
        content: formatFile(task.activeFile),
        source: `file:${task.activeFile.path}`,
        relevance: 1.0,
        tokenCount: countTokens(task.activeFile.content),
        priority: "required",
      });
    }
  }

  private async gatherCandidates(task: CodingTask): Promise<ContextItem[]> {
    const candidates: ContextItem[] = [];

    // Strategy 1: Semantic search over codebase
    const semanticResults = await this.codeIndex.search(
      task.description,
      { limit: 20 }
    );

    for (const result of semanticResults) {
      candidates.push({
        content: formatFile(result.file),
        source: `semantic:${result.file.path}`,
        relevance: result.score,
        tokenCount: countTokens(result.file.content),
        priority: result.score > 0.8 ? "high" : "medium",
      });
    }

    // Strategy 2: Symbol graph (imports, call sites, type definitions)
    if (task.activeFile) {
      const symbols = await this.lsp.getRelatedSymbols(task.activeFile.path);

      for (const symbol of symbols) {
        candidates.push({
          content: formatSymbol(symbol),
          source: `symbol:${symbol.name}@${symbol.file}`,
          relevance: symbol.distance <= 1 ? 0.9 : 0.7,
          tokenCount: countTokens(symbol.definition),
          priority: symbol.distance <= 1 ? "high" : "medium",
        });
      }
    }

    // Strategy 3: Related test files
    const testFiles = await this.findRelatedTests(task);
    for (const test of testFiles) {
      candidates.push({
        content: formatFile(test),
        source: `test:${test.path}`,
        relevance: 0.75,
        tokenCount: countTokens(test.content),
        priority: "medium",
      });
    }

    // Strategy 4: Git blame / recent changes
    const recentChanges = await this.git.getRecentChanges(
      task.activeFile?.path,
      { days: 7 }
    );

    for (const change of recentChanges) {
      candidates.push({
        content: formatDiff(change),
        source: `git:${change.sha.slice(0, 8)}`,
        relevance: 0.6,
        tokenCount: countTokens(change.diff),
        priority: "low",
      });
    }

    return candidates;
  }

  private fillBudget(candidates: ContextItem[]): void {
    // Sort by priority, then relevance
    const priorityOrder = { required: 0, high: 1, medium: 2, low: 3 };

    candidates.sort((a, b) => {
      const pDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
      if (pDiff !== 0) return pDiff;
      return b.relevance - a.relevance;
    });

    let usedTokens = this.items.reduce((sum, i) => sum + i.tokenCount, 0);

    for (const candidate of candidates) {
      if (usedTokens + candidate.tokenCount > this.budget) {
        // Try truncating large files
        if (candidate.tokenCount > 2000) {
          const truncated = this.truncateToFit(
            candidate,
            this.budget - usedTokens
          );
          if (truncated) {
            this.items.push(truncated);
            usedTokens += truncated.tokenCount;
          }
        }
        continue;
      }

      this.items.push(candidate);
      usedTokens += candidate.tokenCount;
    }
  }

  private truncateToFit(
    item: ContextItem,
    available: number
  ): ContextItem | null {
    if (available < 500) return null;

    // Keep first and last portions (function signatures + key logic)
    const lines = item.content.split("\n");
    const keepLines = Math.floor(available / 4); // ~4 tokens per line
    const head = lines.slice(0, keepLines / 2);
    const tail = lines.slice(-keepLines / 2);
    const truncated = [...head, "\n// ... (truncated) ...\n", ...tail].join("\n");

    return {
      ...item,
      content: truncated,
      tokenCount: countTokens(truncated),
    };
  }
}

编辑应用策略

Diff 生成与应用

Agentic Coding Assistant 最关键的工程挑战之一:让模型生成的修改准确地应用到代码文件上。

// src/edit/edit_applicator.ts

interface FileEdit {
  filePath: string;
  editType: "create" | "modify" | "delete" | "rename";
  searchContent?: string;   // old content to find
  replaceContent?: string;  // new content to replace with
  description: string;
}

class EditApplicator {
  /**
   * Apply edits with fuzzy matching and conflict detection.
   *
   * Key insight: LLM-generated diffs are often slightly wrong
   * (whitespace, line numbers, missing context). We need fuzzy
   * matching to handle these imperfections.
   */
  async applyEdits(edits: FileEdit[]): Promise<EditResult[]> {
    const results: EditResult[] = [];

    // Sort: creates first, then modifies, then deletes
    const sorted = this.sortEdits(edits);

    for (const edit of sorted) {
      try {
        const result = await this.applyOne(edit);
        results.push(result);
      } catch (error) {
        results.push({
          filePath: edit.filePath,
          success: false,
          error: error.message,
          suggestion: this.suggestFix(edit, error),
        });
      }
    }

    return results;
  }

  private async applyOne(edit: FileEdit): Promise<EditResult> {
    switch (edit.editType) {
      case "create":
        return this.createFile(edit);

      case "modify":
        return this.modifyFile(edit);

      case "delete":
        return this.deleteFile(edit);

      case "rename":
        return this.renameFile(edit);
    }
  }

  private async modifyFile(edit: FileEdit): Promise<EditResult> {
    const currentContent = await fs.readFile(edit.filePath, "utf-8");

    if (!edit.searchContent || !edit.replaceContent) {
      throw new Error("modify edit requires searchContent and replaceContent");
    }

    // Strategy 1: Exact match
    if (currentContent.includes(edit.searchContent)) {
      const newContent = currentContent.replace(
        edit.searchContent,
        edit.replaceContent
      );
      await fs.writeFile(edit.filePath, newContent);
      return { filePath: edit.filePath, success: true, method: "exact" };
    }

    // Strategy 2: Whitespace-normalized match
    const normalized = this.normalizeWhitespace(edit.searchContent);
    const lines = currentContent.split("\n");
    const match = this.fuzzyFindBlock(lines, normalized);

    if (match) {
      const newLines = [
        ...lines.slice(0, match.startLine),
        edit.replaceContent,
        ...lines.slice(match.endLine + 1),
      ];
      await fs.writeFile(edit.filePath, newLines.join("\n"));
      return { filePath: edit.filePath, success: true, method: "fuzzy" };
    }

    // Strategy 3: Line-by-line diff application
    const patchResult = this.applyPatch(currentContent, edit);
    if (patchResult.success) {
      await fs.writeFile(edit.filePath, patchResult.content);
      return { filePath: edit.filePath, success: true, method: "patch" };
    }

    throw new Error(
      `Cannot locate edit target in ${edit.filePath}. ` +
      `Search content not found (tried exact, fuzzy, and patch).`
    );
  }

  private fuzzyFindBlock(
    lines: string[],
    target: string,
    threshold: number = 0.8
  ): { startLine: number; endLine: number } | null {
    const targetLines = target.split("\n").map(l => l.trim());

    for (let i = 0; i <= lines.length - targetLines.length; i++) {
      let matchCount = 0;

      for (let j = 0; j < targetLines.length; j++) {
        const similarity = this.lineSimilarity(
          lines[i + j].trim(),
          targetLines[j]
        );
        if (similarity > threshold) matchCount++;
      }

      const matchRatio = matchCount / targetLines.length;
      if (matchRatio > threshold) {
        return { startLine: i, endLine: i + targetLines.length - 1 };
      }
    }

    return null;
  }

  private lineSimilarity(a: string, b: string): number {
    if (a === b) return 1.0;
    if (a.length === 0 || b.length === 0) return 0;

    // Levenshtein distance normalized to 0-1
    const maxLen = Math.max(a.length, b.length);
    const distance = levenshtein(a, b);
    return 1 - distance / maxLen;
  }
}

编辑策略对比

策略 原理 优点 缺点
Search/Replace 搜索旧文本,替换为新文本 精确、可审阅 依赖精确匹配
Unified Diff 标准 diff 格式 工具链成熟 行号敏感、易出错
Full File Rewrite 输出完整文件内容 无匹配问题 Token 浪费严重
AST Patch 基于语法树的结构化修改 语义准确 实现复杂、语言相关
Cursor-style Edit 标记块替换(老/新对照) 直观、易审阅 需要自定义解析

生产系统通常采用 Search/Replace + Fuzzy Matching 作为主力,Full File Rewrite 作为兜底。

测试驱动循环

自动修复循环

# src/agent/coding_loop.py

class CodingAgent:
    """Agentic coding loop with test-driven verification."""

    MAX_ITERATIONS = 5

    async def execute_task(self, task: CodingTask) -> TaskResult:
        """Execute a coding task with iterative refinement."""

        # Phase 1: Plan
        plan = await self.plan(task)

        # Phase 2: Implement + Verify loop
        for iteration in range(self.MAX_ITERATIONS):
            # Generate code changes
            edits = await self.generate_edits(task, plan, iteration)

            # Apply edits
            apply_results = await self.applicator.apply_edits(edits)
            failed_applies = [r for r in apply_results if not r.success]

            if failed_applies:
                # Ask LLM to fix application failures
                task.add_context(
                    "edit_failures",
                    self._format_failures(failed_applies),
                )
                continue

            # Run verification suite
            verification = await self.verify(task)

            if verification.all_passed:
                return TaskResult(
                    status="success",
                    edits=edits,
                    iterations=iteration + 1,
                    verification=verification,
                )

            # Feed errors back to LLM for next iteration
            task.add_context(
                "verification_errors",
                self._format_errors(verification),
            )

        return TaskResult(
            status="max_iterations_exceeded",
            edits=edits,
            iterations=self.MAX_ITERATIONS,
            verification=verification,
        )

    async def plan(self, task: CodingTask) -> Plan:
        """Plan the implementation strategy before writing code."""

        context = await self.context_builder.buildContext(task)

        response = await self.llm.chat(
            messages=[
                {"role": "system", "content": PLANNER_PROMPT},
                {"role": "user", "content": context + "\n\nTask: " + task.description},
            ],
            temperature=0,
        )

        return Plan.parse(response.content)

    async def verify(self, task: CodingTask) -> VerificationResult:
        """Run all verification checks."""

        checks = []

        # Check 1: TypeScript compilation
        tsc_result = await self.shell.run("npx tsc --noEmit", timeout=30)
        checks.append(Check(
            name="typescript",
            passed=tsc_result.exit_code == 0,
            output=tsc_result.stderr,
        ))

        # Check 2: Linting
        lint_result = await self.shell.run("npx eslint --fix .", timeout=30)
        checks.append(Check(
            name="lint",
            passed=lint_result.exit_code == 0,
            output=lint_result.stderr,
        ))

        # Check 3: Tests
        test_result = await self.shell.run("npx vitest run", timeout=120)
        checks.append(Check(
            name="tests",
            passed=test_result.exit_code == 0,
            output=test_result.stdout + test_result.stderr,
        ))

        # Check 4: Build
        build_result = await self.shell.run("npm run build", timeout=120)
        checks.append(Check(
            name="build",
            passed=build_result.exit_code == 0,
            output=build_result.stderr,
        ))

        return VerificationResult(
            checks=checks,
            all_passed=all(c.passed for c in checks),
        )

验证循环架构

┌──────────────────────────────────────────────────────────┐
│                   Test-Driven Coding Loop                 │
│                                                          │
│   Task ──→ Plan ──→ Generate Edits ──→ Apply ──→ Verify  │
│                         ▲                         │      │
│                         │         ┌───────────────┘      │
│                         │         ▼                      │
│                         │    All passed? ──Yes──→ Done    │
│                         │         │                      │
│                         │        No                      │
│                         │         │                      │
│                         │         ▼                      │
│                         │    iteration < max?            │
│                         │         │                      │
│                         │        Yes                     │
│                         │         │                      │
│                         └─── Feed errors back            │
│                                                          │
│   Verification Suite:                                    │
│   [TypeScript] [ESLint] [Tests] [Build] [Security]       │
└──────────────────────────────────────────────────────────┘

代码库索引

多层索引架构

# src/index/codebase_index.py

class CodebaseIndex:
    """Multi-layer index for efficient code retrieval."""

    def __init__(self, project_root: str):
        self.root = project_root
        self.file_index = FileIndex()       # file paths + metadata
        self.symbol_index = SymbolIndex()   # functions, classes, types
        self.semantic_index = SemanticIndex() # embedding-based search
        self.graph_index = GraphIndex()      # import/call graph

    async def build(self) -> IndexStats:
        """Build all index layers."""

        files = await self.scan_files()
        stats = IndexStats()

        # Layer 1: File metadata index
        for file in files:
            self.file_index.add(file.path, {
                "language": detect_language(file.path),
                "size": file.size,
                "modified": file.mtime,
            })
            stats.files += 1

        # Layer 2: Symbol extraction (via tree-sitter)
        for file in files:
            if not is_code_file(file.path):
                continue

            symbols = extract_symbols(file.content, file.language)
            for sym in symbols:
                self.symbol_index.add(sym)
                stats.symbols += 1

        # Layer 3: Semantic embeddings
        chunks = self.chunk_files(files)
        embeddings = await self.embed_batch(chunks)
        for chunk, embedding in zip(chunks, embeddings):
            self.semantic_index.add(chunk.id, embedding, chunk.metadata)
            stats.chunks += 1

        # Layer 4: Dependency graph
        for file in files:
            imports = extract_imports(file.content, file.language)
            for imp in imports:
                resolved = resolve_import(imp, file.path, self.root)
                if resolved:
                    self.graph_index.add_edge(file.path, resolved)
                    stats.edges += 1

        return stats

    async def query(
        self,
        query: str,
        file_path: str | None = None,
        limit: int = 10,
    ) -> list[CodeSnippet]:
        """Multi-strategy code retrieval."""

        results = []

        # Strategy 1: Semantic search
        semantic = await self.semantic_index.search(query, limit=limit * 2)
        results.extend(semantic)

        # Strategy 2: Symbol search (for function/class names)
        if self._looks_like_symbol(query):
            symbols = self.symbol_index.search(query)
            results.extend(symbols)

        # Strategy 3: Graph neighbors (if we have a file context)
        if file_path:
            neighbors = self.graph_index.get_neighbors(file_path, depth=2)
            for neighbor in neighbors[:5]:
                content = await self.read_file(neighbor)
                results.append(CodeSnippet(
                    path=neighbor,
                    content=content,
                    relevance=0.7,
                    source="graph",
                ))

        # Deduplicate and rank
        return self.rank_and_dedupe(results, limit)

    def _looks_like_symbol(self, query: str) -> bool:
        """Detect if query is a symbol name (camelCase, snake_case, etc.)."""
        import re
        return bool(re.match(
            r'^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$',
            query.strip()
        ))

索引策略对比

索引层 数据源 检索方式 适用查询
文件索引 文件路径/元数据 精确匹配/glob "找到 auth 相关文件"
符号索引 AST 提取的函数/类/类型 前缀/模糊匹配 "getUserById 在哪里定义"
语义索引 代码块的 embedding 向量相似度 "处理支付回调的代码"
依赖图 import/require 关系 图遍历 "修改 User 模型会影响哪些文件"

安全沙盒

执行隔离

Agentic Coding Assistant 需要运行用户代码(测试、构建、linter),这意味着它可以执行任意命令。沙盒隔离是安全底线。

// src/sandbox/sandbox.ts

interface SandboxConfig {
  timeout: number;         // max execution time (ms)
  memoryLimit: string;     // e.g., "512m"
  networkAccess: boolean;  // allow outbound network
  writablePaths: string[]; // paths the agent can write to
  readonlyPaths: string[]; // paths the agent can only read
}

class DockerSandbox {
  /**
   * Execute commands in an isolated Docker container.
   * The codebase is mounted read-write, but only in the project dir.
   */
  async execute(
    command: string,
    config: SandboxConfig
  ): Promise<ExecutionResult> {

    const containerConfig = {
      image: "coding-agent-sandbox:latest",
      command: ["bash", "-c", command],
      hostConfig: {
        Memory: this.parseMemory(config.memoryLimit),
        NetworkMode: config.networkAccess ? "bridge" : "none",
        Binds: [
          // Project directory: read-write
          ...config.writablePaths.map(p => `${p}:${p}:rw`),
          // System paths: read-only
          ...config.readonlyPaths.map(p => `${p}:${p}:ro`),
        ],
        // No access to host Docker socket
        // No privileged mode
        // No host PID/IPC namespace
      },
    };

    const container = await this.docker.createContainer(containerConfig);
    await container.start();

    // Enforce timeout
    const timeoutId = setTimeout(async () => {
      await container.kill();
    }, config.timeout);

    try {
      const result = await container.wait();
      const logs = await container.logs({ stdout: true, stderr: true });

      return {
        exitCode: result.StatusCode,
        stdout: logs.stdout,
        stderr: logs.stderr,
        timedOut: false,
      };
    } catch (error) {
      return {
        exitCode: -1,
        stdout: "",
        stderr: error.message,
        timedOut: true,
      };
    } finally {
      clearTimeout(timeoutId);
      await container.remove({ force: true });
    }
  }
}

权限模型

┌────────────────────────────────────────────────────────────┐
│                    权限分层模型                              │
│                                                            │
│   Level 0: Read-Only                                       │
│   ├── 读取任意项目文件                                      │
│   ├── 搜索代码库                                            │
│   ├── 查看 git 历史                                         │
│   └── 运行只读命令(ls, cat, grep)                         │
│                                                            │
│   Level 1: Code Edit (需用户确认)                           │
│   ├── 创建/修改/删除项目文件                                │
│   ├── 运行测试和 linter                                     │
│   └── 执行 git add/commit                                  │
│                                                            │
│   Level 2: Shell Access (需用户确认)                        │
│   ├── 安装依赖(npm install)                               │
│   ├── 运行构建脚本                                          │
│   └── 执行项目定义的脚本                                    │
│                                                            │
│   Level 3: System (禁止自动执行)                            │
│   ├── 修改系统文件                                          │
│   ├── 网络请求到外部服务                                    │
│   ├── git push / deploy                                    │
│   └── 安装全局包                                            │
└────────────────────────────────────────────────────────────┘

Prompt 工程

System Prompt 设计

# src/prompts/system.py

CODING_AGENT_SYSTEM_PROMPT = """You are an expert software engineer working as a coding assistant.

## Core Principles
1. Read before you write. Always examine existing code patterns before proposing changes.
2. Minimal changes. Make the smallest diff that solves the problem correctly.
3. Verify your work. After every edit, run the relevant tests and type checker.
4. Explain your reasoning. State WHY you're making each change, not just WHAT.

## Edit Format
When you need to modify files, use the following format:

<edit>
<file>path/to/file.ts</file>
<search>
// exact content to find (include enough context for unique matching)
</search>
<replace>
// new content to replace with
</replace>
<description>Brief explanation of the change</description>
</edit>

## Tool Usage
You have access to the following tools:
- `read_file(path)`: Read a file's contents
- `search_code(query)`: Semantic search across the codebase
- `run_command(cmd)`: Execute a shell command in the project sandbox
- `list_files(path, pattern)`: List files matching a glob pattern
- `get_symbols(path)`: Get function/class definitions in a file

## Rules
- Never modify test assertions to make tests pass
- Never introduce backwards-incompatible changes without explicit approval
- Always check for similar patterns elsewhere when fixing a bug
- If you're unsure about a change, explain the options and ask
- Commit messages should explain WHY, not repeat WHAT the diff says
"""

错误反馈模板

ERROR_FEEDBACK_TEMPLATE = """The following verification checks failed after applying your changes:

{error_details}

Previous attempt (iteration {iteration}/{max_iterations}):
{previous_edits_summary}

Instructions:
1. Analyze the root cause of each failure
2. Do NOT just suppress errors (e.g., adding @ts-ignore)
3. If the approach is fundamentally wrong, propose a different strategy
4. Apply minimal fixes to address each error
5. Ensure your fixes don't break other tests

Your corrected edits:
"""

设计清单

检查项 要求 优先级
上下文预算管理 有明确的 token 预算分配策略 必需
多层索引 文件 + 符号 + 语义 + 依赖图 必需
编辑模糊匹配 容忍 LLM 输出的轻微偏差 必需
测试驱动循环 每次编辑后自动验证 必需
最大迭代保护 防止无限修复循环 必需
执行沙盒 命令在隔离环境中运行 必需
权限分层 读/写/执行/系统分级授权 必需
增量索引 文件变更后只更新受影响的索引 推荐
错误反馈格式 结构化的错误信息传回模型 必需
回滚能力 编辑失败时可回退到上一状态 推荐

总结

  1. 上下文工程是核心竞争力:代码库有百万行,模型只能看到 128K token。谁能在有限预算内塞入最相关的信息,谁的 Agent 就更准确。多层索引(文件 + 符号 + 语义 + 依赖图)是必需的基础设施。
  2. 编辑应用需要容错:LLM 生成的代码修改不可能 100% 精确匹配原文件。Fuzzy matching + 多级回退(精确 -> 归一化 -> patch)是生产级必备。
  3. 测试驱动循环是质量保障:每次编辑后自动运行编译/lint/测试,将错误信息反馈给模型进行下一轮修复。最大迭代次数是防止无限循环的安全网。
  4. 沙盒隔离是安全底线:Agent 能执行任意命令,必须在隔离环境中运行。权限分层(读/写/执行/系统)确保风险可控。
  5. 规划先于编码:好的 Coding Agent 不是直接开始写代码,而是先分析代码库模式、规划修改策略、识别影响范围,然后才执行最小化的精确修改。

Maurice | maurice_wen@proton.me