EXECUTIVE READING
先给结论,再进入源码
核心机制 交互外环 + 有界 reflection;固定 lint/test/commit 交付链
上下文 ChatChunks + 符号图 PageRank Repo Map + 二分 token 预算
适用建设 强调可审查 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 01 Aider 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
本报告引用过的实现文件
这是一份代码阅读索引,不是仓库文件总表。机器候选扫描覆盖整个仓库;进入结论的文件必须经人工沿调用链复核。
01 aider/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 02 aider/models.pyL985–1037, 339–358, 625–645, 1006–1009 03 aider/repomap.pyL300–363, 365–545, 629–706 04 aider/history.pyL27–96, 98–123 05 aider/coders/patch_coder.pyL210–217 06 aider/coders/udiff_coder.pyL46–49 07 aider/run_cmd.pyL42–84, 89–128 08 aider/commands.pyL558–646, 312–332 09 aider/coders/architect_coder.pyL6–48 10 aider/io.pyL754–765, 1117–1136 11 aider/analytics.pyL119–204, 213–254