CODING AGENT HARNESS · SOURCE AUDITREPORT 02 / 18
02

Aider

以 Git、Repo Map 和可替换编辑协议取胜;不是工具调用型自治平台。

Python · Git-native Pair ProgrammerApache-2.0main
SOURCE
VERIFIED
Repository
Aider-AI/aider
Commit
5dc9490bb35f9729ef2c95d00a19ccd30c26339c
Commit date
2026-05-22T07:02:20-07:00
Findings
16
Citations
32
Tracked files
691
EXECUTIVE READING

先给结论,再进入源码

核心机制

交互外环 + 有界 reflection;固定 lint/test/commit 交付链

上下文

ChatChunks + 符号图 PageRank Repo Map + 二分 token 预算

安全边界

宿主 shell;聊天文件集是主要写边界

适用建设

强调可审查 diff、Git 工作流、低复杂度结对编程

值得借鉴

  • Repo Map 算法成熟
  • 编辑事务与 Git 恢复强
  • 协议简单可解释

需要警惕

  • 无 OS 沙箱
  • 连接器/多 Agent 平台能力弱
  • 自治长任务与并发工具有限

直接带走

  • Repo Map 的预算化检索
  • dry-run 编辑事务
  • Git 作为恢复原语
00 · METHOD

研究口径:先锁提交,再沿运行链读代码

README POLICY

README 不承担核心事实证明;结论来自 Coder、RepoMap、编辑器、命令执行、模型和测试实现。

FACT POLICY

以运行时代码和类型契约为主,提示模板只用于解释编辑协议,不反推不存在的隔离能力。

INFERENCE POLICY

“不是通用工具 Agent”“没有 OS 沙箱”等否定性判断由完整调用链与代码搜索共同支撑,并标注限制范围。

L1运行实现
L2接口契约
L3测试证明
L4文档佐证
L5明确推断

本页引用 11 个不同源码/测试文件;证据角色分布:契约 5 · 实现 27。代码块是固定提交中的原文截取,长区间仅在中部折叠,首尾行号保持真实。

01 · TECHNICAL MAPS

架构总图与单轮执行链路

两张图均由本页证据账本生成,并通过 Archify showcase 9 项校验(0 error / 0 warning)。图可单独打开、搜索、缩放和追踪关系。

FIGURE 01Aider Harness 架构图全屏打开 ↗
FIGURE 02用户输入到工具回写的技术链路全屏打开 ↗
02 · COVERAGE MAP

审计维度与证据等级

入口、会话与主循环 verified L1 / L3

已审计 Coder.run/run_one/send_message 及 reflection 上限。

Provider、流式与重试 verified L1 / L2

已审计 Model.send_completion、LiteLLM 与流/非流处理。

上下文与 Repo Map verified L1 / L2 / L3

已审计 ChatChunks、RepoMap、历史摘要和 token 门。

编辑协议与落盘 verified L1 / L2 / L3

已审计多编辑格式基类、dry-run、授权与反思修复。

执行环境与沙箱 verified L1

已审计 shell=True/pexpect 宿主执行;无内置 OS 隔离。

权限与安全 verified L1 / L3

已审计文件加入、越界编辑和 shell 建议的人工确认。

指令与 Prompt verified L1 / L2

已审计按 edit format 选择的 prompt 类和消息装配顺序。

工具、连接器与插件 verified L1 / L2

工具面以 CLI 命令、Git、lint/test、网页抓取为主;当前核心无 MCP/Skills/插件总线。

协作 verified L1 / L2

Architect/Editor 是顺序双模型管线,不是递归子 Agent 系统。

持久化与观测 verified L1 / L2

已审计 markdown 历史、LLM 日志、成本与可选 PostHog。

测试、评测与成熟度 partial L2 / L3

编辑格式和 RepoMap 测试已定位;完整 benchmark 体系待报告章节汇总。

01
DIMENSION · ENTRY-SESSION-LOOP

入口、会话与主循环

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

01
L1事实aider-loop-001

交互外环 + 有界 reflection 内环

源码事实

Coder.run 持续读取用户输入;每个输入由 run_one 调用 send_message,若编辑解析、lint 或 test 产生 reflected_message,则最多再反思 3 次。

白话解释

Aider 的循环很直接:你说一次,它生成修改;修改格式错了或检查失败,就把错误原样喂回模型再试,但不会无限自修。

对自研 Harness 的含义

这是“以编辑反馈驱动的局部循环”,而非任意工具调用状态机。

关键源码 · 契约
aider/coders/base_coder.py · L88–L106
   88  class Coder:
   89      abs_fnames = None
   90      abs_read_only_fnames = None
   91      repo = None
   92      last_aider_commit_hash = None
   93      aider_edited_files = None
   94      last_asked_for_commit_time = 0
   95      repo_map = None
   96      functions = None
   97      num_exhausted_context_windows = 0
   98      num_malformed_responses = 0
   99      last_keyboard_interrupt = None
  100      num_reflections = 0
  101      max_reflections = 3
  102      edit_format = None
  103      yield_stream = False
  104      temperature = None
  105      auto_lint = True
  106      auto_test = False
查看全部 2 处证据
  • 契约 aider/coders/base_coder.py:88–106 max_reflections=3,并定义 auto_lint/auto_test。
  • 实现 aider/coders/base_coder.py:876–944 交互外环与 reflected_message 内环。
02
L1事实aider-loop-002

一次回复后的固定交付链

源码事实

模型回复后依次检查文件提及、解析并应用编辑、自动提交、自动 lint、执行建议 shell、可选 test;错误可转成下一轮 reflected_message。

白话解释

Aider 更像一条固定流水线,不是让模型临场决定下一步用什么工具。

对自研 Harness 的含义

确定性强、容易理解,但扩展到浏览器、工单、云资源等非代码工具时不如通用 tool-loop 灵活。

关键源码 · 实现
aider/coders/base_coder.py · L1530–L1623
 1530          self.io.tool_output()
 1531  
 1532          self.show_usage_report()
 1533  
 1534          self.add_assistant_reply_to_cur_messages()
 1535  
 1536          if exhausted:
 1537              if self.cur_messages and self.cur_messages[-1]["role"] == "user":
 1538                  self.cur_messages += [
 1539                      dict(
 1540                          role="assistant",
 1541                          content="FinishReasonLength exception: you sent too many tokens",
 1542                      ),
 1543                  ]
 1544  
 1545              self.show_exhausted_error()
 1546              self.num_exhausted_context_windows += 1
 1547              return
 1548  
 1549          if self.partial_response_function_call:
 1550              args = self.parse_partial_args()
 1551              if args:
 1552                  content = args.get("explanation") or ""
 1553              else:
      … 60 lines omitted; exact range 1530–1623 …
 1614              ]
 1615  
 1616          if edited and self.auto_test:
 1617              test_errors = self.commands.cmd_test(self.test_cmd)
 1618              self.test_outcome = not test_errors
 1619              if test_errors:
 1620                  ok = self.io.confirm_ask("Attempt to fix test errors?")
 1621                  if ok:
 1622                      self.reflected_message = test_errors
 1623                      return
查看全部 1 处证据
  • 实现 aider/coders/base_coder.py:1530–1623 回复落地后的 edit→commit→lint→shell→test 链。
02
DIMENSION · PROVIDER-STREAMING

Provider、流式与重试

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

03
L1事实aider-provider-001

LiteLLM 是统一 Provider 适配层

源码事实

Model.send_completion 把模型名、messages、stream、temperature、可选单个 function tool 和 extra params 交给 litellm.completion;流式和非流式结果由 Coder 分别消费。

白话解释

Aider 把各家模型 API 的差异交给 LiteLLM,自己的核心只面对一套近似 OpenAI 的消息格式。

对自研 Harness 的含义

接模型很快,但 Provider 行为、重试和能力元数据也部分受 LiteLLM 语义约束。

关键源码 · 实现
aider/models.py · L985–L1037
  985      def send_completion(self, messages, functions, stream, temperature=None):
  986          if os.environ.get("AIDER_SANITY_CHECK_TURNS"):
  987              sanity_check_messages(messages)
  988  
  989          if self.is_deepseek_r1():
  990              messages = ensure_alternating_roles(messages)
  991  
  992          kwargs = dict(
  993              model=self.name,
  994              stream=stream,
  995          )
  996  
  997          if self.use_temperature is not False:
  998              if temperature is None:
  999                  if isinstance(self.use_temperature, bool):
 1000                      temperature = 0
 1001                  else:
 1002                      temperature = float(self.use_temperature)
 1003  
 1004              kwargs["temperature"] = temperature
 1005  
 1006          if functions is not None:
 1007              function = functions[0]
 1008              kwargs["tools"] = [dict(type="function", function=function)]
      … 19 lines omitted; exact range 985–1037 …
 1028              if "extra_headers" not in kwargs:
 1029                  kwargs["extra_headers"] = {
 1030                      "Editor-Version": f"aider/{__version__}",
 1031                      "Copilot-Integration-Id": "vscode-chat",
 1032                  }
 1033  
 1034              self.github_copilot_token_to_open_ai_key(kwargs["extra_headers"])
 1035  
 1036          res = litellm.completion(**kwargs)
 1037          return hash_object, res
查看全部 2 处证据
  • 实现 aider/models.py:985–1037 构造 kwargs 并调用 litellm.completion。
  • 实现 aider/coders/base_coder.py:1783–1826 send_completion 后按 stream 分派输出,并记录成本和 LLM 历史。
03
DIMENSION · CONTEXT-REPOMAP

上下文与 Repo Map

本章共 5 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

04
L1事实aider-context-001

上下文被拆成稳定的 ChatChunks

源码事实

每次请求按 system、examples、已摘要历史、repo map、只读文件、可编辑文件、当前消息和 reminder 的顺序组装。

白话解释

Aider 不把所有材料乱塞成一团,而是把“规则、示例、历史、仓库地图、文件正文、当前问题”分舱装箱。

对自研 Harness 的含义

分舱让缓存、token 统计和不同模型的 system-message 兼容更可控。

关键源码 · 实现
aider/coders/base_coder.py · L1226–L1338
 1226      def format_chat_chunks(self):
 1227          self.choose_fence()
 1228          main_sys = self.fmt_system_prompt(self.gpt_prompts.main_system)
 1229          if self.main_model.system_prompt_prefix:
 1230              main_sys = self.main_model.system_prompt_prefix + "\n" + main_sys
 1231  
 1232          example_messages = []
 1233          if self.main_model.examples_as_sys_msg:
 1234              if self.gpt_prompts.example_messages:
 1235                  main_sys += "\n# Example conversations:\n\n"
 1236              for msg in self.gpt_prompts.example_messages:
 1237                  role = msg["role"]
 1238                  content = self.fmt_system_prompt(msg["content"])
 1239                  main_sys += f"## {role.upper()}: {content}\n\n"
 1240              main_sys = main_sys.strip()
 1241          else:
 1242              for msg in self.gpt_prompts.example_messages:
 1243                  example_messages.append(
 1244                      dict(
 1245                          role=msg["role"],
 1246                          content=self.fmt_system_prompt(msg["content"]),
 1247                      )
 1248                  )
 1249              if self.gpt_prompts.example_messages:
      … 79 lines omitted; exact range 1226–1338 …
 1329                  chunks.cur[-1] = dict(role=final["role"], content=new_content)
 1330  
 1331          return chunks
 1332  
 1333      def format_messages(self):
 1334          chunks = self.format_chat_chunks()
 1335          if self.add_cache_headers:
 1336              chunks.add_cache_control_headers()
 1337  
 1338          return chunks
查看全部 1 处证据
  • 实现 aider/coders/base_coder.py:1226–1338 构造 ChatChunks 并在空间允许时追加 reminder。
05
L1事实aider-repomap-001

Repo Map 是基于符号引用图的 PageRank

源码事实

RepoMap 用 tree-sitter 查询提取定义/引用,缺少引用时用 Pygments 补齐;构建文件间 MultiDiGraph,并以当前聊天文件、用户提及和符号特征调权后运行 PageRank。

白话解释

它不是简单列目录,而是判断“哪些文件定义了被很多地方引用的名字、哪些又和当前问题相关”,再把最重要的代码骨架给模型。

对自研 Harness 的含义

这是 Aider 最有辨识度的 Harness 能力:用静态代码图在有限 token 内提供全仓导航。

关键源码 · 实现
aider/repomap.py · L300–L363
  300  
  301          # Run the tags queries
  302          captures = self._run_captures(Query(language, query_scm), tree.root_node)
  303  
  304          captures_by_tag = defaultdict(list)
  305          matches = []
  306          for tag, nodes in captures.items():
  307              for node in nodes:
  308                  captures_by_tag[tag].append(node)
  309              captures_by_tag[tag].append(node)
  310              matches.append((node, tag))
  311  
  312          if USING_TSL_PACK:
  313              all_nodes = [(node, tag) for tag, nodes in captures_by_tag.items() for node in nodes]
  314          else:
  315              all_nodes = matches
  316  
  317          saw = set()
  318          for node, tag in all_nodes:
  319              if tag.startswith("name.definition."):
  320                  kind = "def"
  321              elif tag.startswith("name.reference."):
  322                  kind = "ref"
  323              else:
      … 30 lines omitted; exact range 300–363 …
  354          tokens = [token[1] for token in tokens if token[0] in Token.Name]
  355  
  356          for token in tokens:
  357              yield Tag(
  358                  rel_fname=rel_fname,
  359                  fname=fname,
  360                  name=token,
  361                  kind="ref",
  362                  line=-1,
  363              )
查看全部 2 处证据
  • 实现 aider/repomap.py:300–363 tree-sitter captures 生成 def/ref,Pygments 回填引用。
  • 实现 aider/repomap.py:365–545 构图、个性化权重、PageRank 和定义分数分配。
06
L1事实aider-repomap-002

Repo Map 用二分搜索贴合 token 预算

源码事实

排序后的 tags 被逐步渲染成代码树,算法用二分搜索选择条目数量,以不超预算且尽可能接近预算为目标,15% 误差内可提前停止。

白话解释

先排好“谁最重要”,再用二分法找出能塞进模型窗口的最大一组,而不是拍脑袋截前 100 个。

对自研 Harness 的含义

检索质量和预算控制被明确分成两个阶段,便于调优。

关键源码 · 实现
aider/repomap.py · L629–L706
  629      def get_ranked_tags_map_uncached(
  630          self,
  631          chat_fnames,
  632          other_fnames=None,
  633          max_map_tokens=None,
  634          mentioned_fnames=None,
  635          mentioned_idents=None,
  636      ):
  637          if not other_fnames:
  638              other_fnames = list()
  639          if not max_map_tokens:
  640              max_map_tokens = self.max_map_tokens
  641          if not mentioned_fnames:
  642              mentioned_fnames = set()
  643          if not mentioned_idents:
  644              mentioned_idents = set()
  645  
  646          spin = Spinner(UPDATING_REPO_MAP_MESSAGE)
  647  
  648          ranked_tags = self.get_ranked_tags(
  649              chat_fnames,
  650              other_fnames,
  651              mentioned_fnames,
  652              mentioned_idents,
      … 44 lines omitted; exact range 629–706 …
  697  
  698              if num_tokens < max_map_tokens:
  699                  lower_bound = middle + 1
  700              else:
  701                  upper_bound = middle - 1
  702  
  703              middle = int((lower_bound + upper_bound) // 2)
  704  
  705          spin.end()
  706          return best_tree
查看全部 1 处证据
  • 实现 aider/repomap.py:629–706 对 ranked_tags 做 token 估算和二分选择。
07
L1事实aider-summary-001

历史摘要保留近期尾部并递归收缩头部

源码事实

ChatSummary 以模型上下文的 1/16(最少 1K、最多 8K)作为默认历史预算;过大时尽量保留约半数预算的最近消息,摘要较旧头部,必要时最多递归 3 层。

白话解释

老对话压成摘要,最近几轮尽量原样保留;压完还太大就再压一次。

对自研 Harness 的含义

比整段一次摘要更重视近期细节,但摘要是自由文本而非结构化任务状态。

关键源码 · 契约
aider/models.py · L339–L358
  339          self.max_chat_history_tokens = 1024
  340          self.weak_model = None
  341          self.editor_model = None
  342  
  343          # Find the extra settings
  344          self.extra_model_settings = next(
  345              (ms for ms in MODEL_SETTINGS if ms.name == "aider/extra_params"), None
  346          )
  347  
  348          self.info = self.get_model_info(model)
  349  
  350          # Are all needed keys/params available?
  351          res = self.validate_environment()
  352          self.missing_keys = res.get("missing_keys")
  353          self.keys_in_environment = res.get("keys_in_environment")
  354  
  355          max_input_tokens = self.info.get("max_input_tokens") or 0
  356          # Calculate max_chat_history_tokens as 1/16th of max_input_tokens,
  357          # with minimum 1k and maximum 8k
  358          self.max_chat_history_tokens = min(max(max_input_tokens / 16, 1024), 8192)
查看全部 3 处证据
  • 契约 aider/models.py:339–358 max_chat_history_tokens 取上下文 1/16,并限制在 1K–8K。
  • 实现 aider/history.py:27–96 保留 tail、摘要 head、必要时递归。
  • 实现 aider/history.py:98–123 按候选模型顺序生成自由文本 summary。
08
L1限制aider-context-002

预测超窗时由用户决定是否硬发

源码事实

check_tokens 若估算输入超过模型上限,会给出 drop/clear/拆文件建议并询问是否仍继续;Provider 真报 ContextWindowExceeded 时结束本轮,不自动做恢复性压缩再发。

白话解释

Aider 会提前报警,但不会偷偷重写上下文;你可以执意发送,失败后自己缩小范围。

对自研 Harness 的含义

行为透明且可控,但长任务自治恢复弱于带自动 compaction 的通用 Agent。

关键源码 · 实现
aider/coders/base_coder.py · L1396–L1417
 1396      def check_tokens(self, messages):
 1397          """Check if the messages will fit within the model's token limits."""
 1398          input_tokens = self.main_model.token_count(messages)
 1399          max_input_tokens = self.main_model.info.get("max_input_tokens") or 0
 1400  
 1401          if max_input_tokens and input_tokens >= max_input_tokens:
 1402              self.io.tool_error(
 1403                  f"Your estimated chat context of {input_tokens:,} tokens exceeds the"
 1404                  f" {max_input_tokens:,} token limit for {self.main_model.name}!"
 1405              )
 1406              self.io.tool_output("To reduce the chat context:")
 1407              self.io.tool_output("- Use /drop to remove unneeded files from the chat")
 1408              self.io.tool_output("- Use /clear to clear the chat history")
 1409              self.io.tool_output("- Break your code into smaller files")
 1410              self.io.tool_output(
 1411                  "It's probably safe to try and send the request, most providers won't charge if"
 1412                  " the context limit is exceeded."
 1413              )
 1414  
 1415              if not self.io.confirm_ask("Try to proceed anyway?"):
 1416                  return False
 1417          return True
查看全部 2 处证据
  • 实现 aider/coders/base_coder.py:1396–1417 估算超窗后询问是否继续。
  • 实现 aider/coders/base_coder.py:1457–1467 ContextWindowExceededError 标记 exhausted 并离开重试循环。
04
DIMENSION · EDITING

编辑协议与落盘

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

09
L2事实aider-edit-001

编辑协议是可替换 Coder 家族

源码事实

Coder.create 按 edit_format 选择 Whole、Diff、Unified Diff、Patch、Function、Architect、Ask、Context 等子类;切换格式时会先摘要旧格式历史,避免模型模仿旧输出协议。

白话解释

Aider 把“模型应该怎样描述改动”做成多种可换的方言,并按模型能力选择最合适的一种。

对自研 Harness 的含义

编辑可靠性可以按模型定制,而不必让所有模型都走同一个 JSON tool schema。

关键源码 · 实现
aider/coders/base_coder.py · L124–L201
  124      @classmethod
  125      def create(
  126          self,
  127          main_model=None,
  128          edit_format=None,
  129          io=None,
  130          from_coder=None,
  131          summarize_from_coder=True,
  132          **kwargs,
  133      ):
  134          import aider.coders as coders
  135  
  136          if not main_model:
  137              if from_coder:
  138                  main_model = from_coder.main_model
  139              else:
  140                  main_model = models.Model(models.DEFAULT_MODEL_NAME)
  141  
  142          if edit_format == "code":
  143              edit_format = None
  144          if edit_format is None:
  145              if from_coder:
  146                  edit_format = from_coder.edit_format
  147              else:
      … 44 lines omitted; exact range 124–201 …
  192                  res = coder(main_model, io, **kwargs)
  193                  res.original_kwargs = dict(kwargs)
  194                  return res
  195  
  196          valid_formats = [
  197              str(c.edit_format)
  198              for c in coders.__all__
  199              if hasattr(c, "edit_format") and c.edit_format is not None
  200          ]
  201          raise UnknownEditFormat(edit_format, valid_formats)
查看全部 3 处证据
  • 实现 aider/coders/base_coder.py:124–201 按 edit_format 查找 Coder 子类,切换时摘要旧历史。
  • 契约 aider/coders/patch_coder.py:210–217 PatchCoder 注册 patch edit_format。
  • 契约 aider/coders/udiff_coder.py:46–49 UnifiedDiffCoder 注册 udiff edit_format。
10
L1事实aider-edit-002

编辑先 dry-run,再授权,再落盘

源码事实

apply_updates 先解析 edits、执行 apply_edits_dry_run、逐文件 allowed_to_edit/prepare_to_edit,最后才 apply_edits;格式错误或异常会转成 reflected_message。

白话解释

模型给出的补丁先试演,确认目标文件允许修改后才真正写;补丁坏了会把错误退回给模型修。

对自研 Harness 的含义

编辑协议的验证和文件授权是两个独立关卡。

关键源码 · 实现
aider/coders/base_coder.py · L2269–L2336
 2269      def prepare_to_edit(self, edits):
 2270          res = []
 2271          seen = dict()
 2272  
 2273          self.need_commit_before_edits = set()
 2274  
 2275          for edit in edits:
 2276              path = edit[0]
 2277              if path is None:
 2278                  res.append(edit)
 2279                  continue
 2280              if path == "python":
 2281                  dump(edits)
 2282              if path in seen:
 2283                  allowed = seen[path]
 2284              else:
 2285                  allowed = self.allowed_to_edit(path)
 2286                  seen[path] = allowed
 2287  
 2288              if allowed:
 2289                  res.append(edit)
 2290  
 2291          self.dirty_commit()
 2292          self.need_commit_before_edits = set()
      … 34 lines omitted; exact range 2269–2336 …
 2327              self.reflected_message = str(err)
 2328              return edited
 2329  
 2330          for path in edited:
 2331              if self.dry_run:
 2332                  self.io.tool_output(f"Did not apply edit to {path} (--dry-run)")
 2333              else:
 2334                  self.io.tool_output(f"Applied edit to {path}")
 2335  
 2336          return edited
查看全部 1 处证据
  • 实现 aider/coders/base_coder.py:2269–2336 dry-run→prepare→apply,并把错误反馈给模型。
05
DIMENSION · PERMISSIONS-SECURITY

权限与安全

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

11
L1事实aider-permission-001

“聊天文件集”就是主要写权限边界

源码事实

模型尝试编辑未加入聊天的文件时,allowed_to_edit 会询问用户;通过后才加入可编辑集合并处理 dirty commit。

白话解释

文件有没有放进聊天,不只是上下文选择,也决定模型能不能直接改它。

对自研 Harness 的含义

Aider 把最重要的权限问题压缩成一个易懂交互,但它不是细粒度路径策略或 RBAC。

关键源码 · 实现
aider/coders/base_coder.py · L2215–L2240
 2215  
 2216                  # Seems unlikely that we needed to create the file, but it was
 2217                  # actually already part of the repo.
 2218                  # But let's only add if we need to, just to be safe.
 2219                  if need_to_add and self.auto_commits:
 2220                      self.repo.repo.git.add(full_path)
 2221  
 2222              self.abs_fnames.add(full_path)
 2223              self.check_added_files()
 2224              return True
 2225  
 2226          if not self.io.confirm_ask(
 2227              "Allow edits to file that has not been added to the chat?",
 2228              subject=path,
 2229          ):
 2230              self.io.tool_output(f"Skipping edits to {path}")
 2231              return
 2232  
 2233          if need_to_add and self.auto_commits:
 2234              self.repo.repo.git.add(full_path)
 2235  
 2236          self.abs_fnames.add(full_path)
 2237          self.check_added_files()
 2238          self.check_for_dirty_commit(path)
 2239  
 2240          return True
查看全部 1 处证据
  • 实现 aider/coders/base_coder.py:2215–2240 未在聊天中的文件需确认后加入可编辑集合。
06
DIMENSION · EXECUTION-SANDBOX

执行环境与沙箱

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

12
L1限制aider-shell-001

Shell 在宿主机执行,没有 OS 沙箱

源码事实

模型建议的 shell 命令需显式 yes 确认,然后由 run_cmd 在项目 cwd 通过 shell=True 或交互式 pexpect 启动用户 shell。

白话解释

命令执行前会问你,但点了同意以后就是在真实终端里跑;Aider 本身没有容器、seccomp 或工作区文件系统隔离。

对自研 Harness 的含义

安全依赖人类确认和外部运行环境;自动化模式需要额外容器或最小权限账户。

关键源码 · 实现
aider/coders/base_coder.py · L2434–L2485
 2434      def run_shell_commands(self):
 2435          if not self.suggest_shell_commands:
 2436              return ""
 2437  
 2438          done = set()
 2439          group = ConfirmGroup(set(self.shell_commands))
 2440          accumulated_output = ""
 2441          for command in self.shell_commands:
 2442              if command in done:
 2443                  continue
 2444              done.add(command)
 2445              output = self.handle_shell_commands(command, group)
 2446              if output:
 2447                  accumulated_output += output + "\n\n"
 2448          return accumulated_output
 2449  
 2450      def handle_shell_commands(self, commands_str, group):
 2451          commands = commands_str.strip().splitlines()
 2452          command_count = sum(
 2453              1 for cmd in commands if cmd.strip() and not cmd.strip().startswith("#")
 2454          )
 2455          prompt = "Run shell command?" if command_count == 1 else "Run shell commands?"
 2456          if not self.io.confirm_ask(
 2457              prompt,
      … 18 lines omitted; exact range 2434–2485 …
 2476              if output:
 2477                  accumulated_output += f"Output from {command}\n{output}\n"
 2478  
 2479          if accumulated_output.strip() and self.io.confirm_ask(
 2480              "Add command output to the chat?", allow_never=True
 2481          ):
 2482              num_lines = len(accumulated_output.strip().splitlines())
 2483              line_plural = "line" if num_lines == 1 else "lines"
 2484              self.io.tool_output(f"Added {num_lines} {line_plural} of output to the chat.")
 2485              return accumulated_output
查看全部 3 处证据
  • 实现 aider/coders/base_coder.py:2434–2485 建议命令必须 explicit_yes_required,随后 run_cmd。
  • 实现 aider/run_cmd.py:42–84 subprocess.Popen(..., shell=True, cwd=cwd)。
  • 实现 aider/run_cmd.py:89–128 TTY 场景通过用户 shell 的 pexpect 交互执行。
07
DIMENSION · PERSISTENCE-RECOVERY

持久化与恢复

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

13
L1事实aider-git-001

Git 提交是编辑事务和恢复机制

源码事实

成功编辑后默认自动提交,修改前的 dirty 文件也可先提交;/undo 只允许撤销当前会话由 Aider 创建的最近提交。

白话解释

Aider 用 Git 当保险箱:改前存一份,改后再存一份,出问题可以退回,但不会随便回滚用户自己的提交。

对自研 Harness 的含义

在代码仓库场景,VCS 原生事务比自造文件快照更透明。

关键源码 · 实现
aider/coders/base_coder.py · L2375–L2423
 2375      def auto_commit(self, edited, context=None):
 2376          if not self.repo or not self.auto_commits or self.dry_run:
 2377              return
 2378  
 2379          if not context:
 2380              context = self.get_context_from_history(self.cur_messages)
 2381  
 2382          try:
 2383              res = self.repo.commit(fnames=edited, context=context, aider_edits=True, coder=self)
 2384              if res:
 2385                  self.show_auto_commit_outcome(res)
 2386                  commit_hash, commit_message = res
 2387                  return self.gpt_prompts.files_content_gpt_edits.format(
 2388                      hash=commit_hash,
 2389                      message=commit_message,
 2390                  )
 2391  
 2392              return self.gpt_prompts.files_content_gpt_no_edits
 2393          except ANY_GIT_ERROR as err:
 2394              self.io.tool_error(f"Unable to commit: {str(err)}")
 2395              return
 2396  
 2397      def show_auto_commit_outcome(self, res):
 2398          commit_hash, commit_message = res
      … 15 lines omitted; exact range 2375–2423 …
 2414          if not self.dirty_commits:
 2415              return
 2416          if not self.repo:
 2417              return
 2418  
 2419          self.repo.commit(fnames=self.need_commit_before_edits, coder=self)
 2420  
 2421          # files changed, move cur messages back behind the files messages
 2422          # self.move_back_cur_messages(self.gpt_prompts.files_content_local_edits)
 2423          return True
查看全部 2 处证据
  • 实现 aider/coders/base_coder.py:2375–2423 auto_commit 与 dirty_commit。
  • 实现 aider/commands.py:558–646 undo 校验提交归属并恢复文件。
08
DIMENSION · COLLABORATION

协作

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

14
L1事实aider-collab-001

Architect/Editor 是顺序双模型链

源码事实

ArchitectCoder 先让架构模型产生方案,用户可确认是否编辑;随后创建 editor_coder,清空其历史,以方案作为唯一任务执行修改,再把成本和提交记录回传。

白话解释

一个模型负责想清楚“怎么改”,另一个模型只拿方案动手;它们不是并行,也不能继续派生更多 Agent。

对自研 Harness 的含义

职责分离能让强推理模型配合擅长补丁的模型,但缺少多任务 fan-out、共享黑板和子任务调度。

关键源码 · 实现
aider/coders/architect_coder.py · L6–L48
    6  class ArchitectCoder(AskCoder):
    7      edit_format = "architect"
    8      gpt_prompts = ArchitectPrompts()
    9      auto_accept_architect = False
   10  
   11      def reply_completed(self):
   12          content = self.partial_response_content
   13  
   14          if not content or not content.strip():
   15              return
   16  
   17          if not self.auto_accept_architect and not self.io.confirm_ask("Edit the files?"):
   18              return
   19  
   20          kwargs = dict()
   21  
   22          # Use the editor_model from the main_model if it exists, otherwise use the main_model itself
   23          editor_model = self.main_model.editor_model or self.main_model
   24  
   25          kwargs["main_model"] = editor_model
   26          kwargs["edit_format"] = self.main_model.editor_edit_format
   27          kwargs["suggest_shell_commands"] = False
   28          kwargs["map_tokens"] = 0
   29          kwargs["total_cost"] = self.total_cost
      … 9 lines omitted; exact range 6–48 …
   39          editor_coder.done_messages = []
   40  
   41          if self.verbose:
   42              editor_coder.show_announcements()
   43  
   44          editor_coder.run(with_message=content, preproc=False)
   45  
   46          self.move_back_cur_messages("I made those changes to the files.")
   47          self.total_cost = editor_coder.total_cost
   48          self.aider_commit_hashes = editor_coder.aider_commit_hashes
查看全部 2 处证据
  • 实现 aider/coders/architect_coder.py:6–48 架构回复确认后创建 Editor Coder 并顺序执行。
  • 契约 aider/models.py:625–645 解析 editor_model 与 editor_edit_format。
09
DIMENSION · TOOLS-CONNECTORS-PLUGINS

工具、连接器与插件

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

15
L1限制aider-tools-001

核心不是 MCP/函数工具循环

源码事实

核心 Coder 的外部能力来自固定 CLI 命令、Git、文件编辑格式、lint/test、网页抓取和可选单一 function edit schema;仓库核心未实现通用 MCP 工具注册与动态插件调度。

白话解释

Aider 专注“在 Git 仓库里改代码”,不是一个可随时挂几十种业务工具的通用 Agent 平台。

对自研 Harness 的含义

专注带来可预测性和编辑深度,连接器生态与跨系统工作流则明显受限。

边界
  • 结论针对该快照的核心仓库;第三方封装可在 Aider 外层增加 MCP。
关键源码 · 实现
aider/models.py · L1006–L1009
 1006          if functions is not None:
 1007              function = functions[0]
 1008              kwargs["tools"] = [dict(type="function", function=function)]
 1009              kwargs["tool_choice"] = {"type": "function", "function": {"name": function["name"]}}
查看全部 2 处证据
  • 实现 aider/models.py:1006–1009 functions 非空时只发送 functions[0] 并强制选择该函数。
  • 实现 aider/commands.py:312–332 命令分发由固定 cmd_* 方法完成。
10
DIMENSION · OBSERVABILITY-PERSISTENCE

持久化与观测

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

16
L1事实aider-observe-001

本地历史、原始 LLM 日志、成本和产品分析分层记录

源码事实

IO 可分别追加输入历史、Markdown 聊天历史和完整 LLM 请求/响应日志;Coder 统计 token/成本;Analytics 经抽样同意后向 PostHog 或本地 JSONL 发事件,并对未知模型名做脱敏。

白话解释

给用户看的聊天记录、调试模型的原始记录、花费统计和匿名产品埋点是四套不同通道。

对自研 Harness 的含义

分层很好,但它不是逐工具 span/trace 的可回放观测系统。

关键源码 · 实现
aider/io.py · L754–L765
  754      def log_llm_history(self, role, content):
  755          if not self.llm_history_file:
  756              return
  757          timestamp = datetime.now().isoformat(timespec="seconds")
  758          try:
  759              Path(self.llm_history_file).parent.mkdir(parents=True, exist_ok=True)
  760              with open(self.llm_history_file, "a", encoding="utf-8") as log_file:
  761                  log_file.write(f"{role.upper()} {timestamp}\n")
  762                  log_file.write(content + "\n")
  763          except (PermissionError, OSError) as err:
  764              self.tool_warning(f"Unable to write to llm history file {self.llm_history_file}: {err}")
  765              self.llm_history_file = None
查看全部 4 处证据
  • 实现 aider/io.py:754–765 可选 llm_history_file 追加原始日志。
  • 实现 aider/io.py:1117–1136 Markdown chat history 追加。
  • 实现 aider/analytics.py:119–204 抽样询问、持久禁用、系统信息与模型名脱敏。
  • 实现 aider/analytics.py:213–254 PostHog 与 JSONL 事件写入。
APPENDIX · SOURCE INDEX

本报告引用过的实现文件

这是一份代码阅读索引,不是仓库文件总表。机器候选扫描覆盖整个仓库;进入结论的文件必须经人工沿调用链复核。

  1. 01aider/coders/base_coder.pyL88–106, 876–944, 1530–1623, 1783–1826, 1226–1338, 1396–1417, 1457–1467, 124–201, 2269–2336, 2215–2240, 2434–2485, 2375–2423
  2. 02aider/models.pyL985–1037, 339–358, 625–645, 1006–1009
  3. 03aider/repomap.pyL300–363, 365–545, 629–706
  4. 04aider/history.pyL27–96, 98–123
  5. 05aider/coders/patch_coder.pyL210–217
  6. 06aider/coders/udiff_coder.pyL46–49
  7. 07aider/run_cmd.pyL42–84, 89–128
  8. 08aider/commands.pyL558–646, 312–332
  9. 09aider/coders/architect_coder.pyL6–48
  10. 10aider/io.pyL754–765, 1117–1136
  11. 11aider/analytics.pyL119–204, 213–254