【免费下载链接】CoreCoder
Minimal AI coding agent (~1,000 lines of Python) inspired by Claude Code. Works with any LLM. Think NanoGPT for coding agents. Formerly NanoCoder.
CoreCoder 是一个仅有约一千行 Python 代码的最小 AI 编程智能体(coding agent),灵感来自 Claude Code,可以连接任意 LLM 自主读写文件、执行命令。当你让它"看看这三个文件分别写了啥"时,它很可能一口气返回三个工具调用——如果逐个串行执行就是干等,而 CoreCoder 的解法正是本文的主角:线程池并发加速多工具调用,以及用**子Agent(sub-agent)**隔离重活。下面带你读懂这套机制的设计与取舍。
背景:模型为什么要"一次要几个工具"
CoreCoder 的核心是一个while循环:把用户的话交给模型 → 执行它要求的工具 → 把结果塞回上下文 → 再问,直到模型不再要工具为止(corecoder/agent.py)。
但模型并不总是一次只要一个工具。比如让它对比三个配置文件,它会同时发出三个read_file调用;这三个读操作彼此独立,串行跑就是白等。于是主循环里做了一个关键的分流:
if len(resp.tool_calls) == 1: # 单个调用:直接同步执行 result = self._exec_tool(tc) else: # 多个调用:交给线程池并发跑 results = self._exec_tools_parallel(resp.tool_calls, on_tool)单个调用走最简单的直跑路径;多个调用则进入_exec_tools_parallel(corecoder/agent.py),这是整个并行机制的心脏。
线程池并发:多工具调用如何同时跑
核心实现只有寥寥数行:
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool: futures = {i: pool.submit(run, i, tc, session_cwd) for i, tc in enumerate(tool_calls)} for i, future in futures.items(): i, results[i], worker_cwd = future.result()最多 8 个工作线程,每个线程跑一个工具,最后按原始顺序收齐结果——顺序一致性对模型很重要,结果要能一一对应回它的每个tool_call_id。
⚡ 为什么选线程池,而不是多进程或协程?
- 工具干的几乎全是IO 密集的活:读盘、起子进程、等网络。这种场景下 Python 的 GIL 基本不碍事,线程就能跑满带宽。
ThreadPoolExecutor是标准库里最省事的并发原语,几行搞定,符合项目"能简则简"的基调。- 三个文件读取本来要等三趟 IO,现在大致只等最慢的那一趟。
并行不是免费的:共享可变状态会咬人
并发最容易出事的地方,是多个任务共享同一份可变状态。CoreCoder 里有两处教科书级的防护。
🧵 bash 的 cd 追踪与线程本地存储
bash 工具需要在多条命令之间记住cd去了哪里(corecoder/tools/bash.py)。最直觉的存法是一个模块级全局变量,串行时毫无问题;可一旦两个 bash 调用在不同线程里同时读写它,一个刚改成目录 A,另一个可能正好读到 A 或覆盖成 B——典型的竞态,平时跑一万次都对,偏偏并发时给你个莫名其妙还难复现的错。
干净的解法是用threading.local()把状态隔离到每个线程:
_local = threading.local() def get_tracked_cwd(): """取当前线程自己追踪的 cwd""" return getattr(_local, "cwd", None)此外,工作线程启动时会继承会话级 cwd,批次结束后再把各线程里的cd按调用顺序合并回来,让"并行的一批 bash"对外表现得像"串行的一串 bash"。相关行为由 tests/test_core.py 中的并行测试守护。
🔒 文件写入的串行锁
并行的另一颗暗雷:两个edit_file同时改一个文件,"读-改-写"交错就会丢编辑。CoreCoder 在工具基类里放了一把全局锁(corecoder/tools/base.py):
FILE_MUTATION_LOCK = threading.Lock()edit_file/write_file执行时都先拿到这把锁,保证同一批并发里的写操作串行落地——两条编辑都不会丢。读类工具则无需加锁,放心并发。
💡 这是加并行前必问的一课:你的工具,扛得住被两个线程同时调用吗?给 agent 加并行,等于给所有带可变状态的工具加了一道并发正确性要求。
权限确认与 Hooks 的"前置结算"
并行还有一个人机交互细节:改文件的工具(edit_file、bash、派生子Agent)执行前要经过用户确认,而且 PreToolUse/PostToolUse hooks 也会围绕每次调用触发。如果让 8 个线程各自弹确认框,终端上会同时冒出好几个 prompt,谁也看不清。
CoreCoder 的做法是:hooks 和权限判定全部在主线程先行结算完,只有放行(或拒绝)的调用才会提交进线程池。被拒绝的调用不会执行,其"拒绝理由"直接作为普通工具结果返回给模型,主循环因此永远不会卡死在等输入上。
另外,即使你在并行批次执行中途按下 Ctrl+C,主循环也会给每个还没回音的tool_calls补一条占位回复(见 corecoder/agent.py),保证历史消息始终合法,下一轮请求不会被"孤儿消息"污染。
子Agent机制:给主Agent开一个"分身"
如果说线程池解决的是"一次多干几件小事",那么agent工具(corecoder/tools/agent.py,72 行)解决的是"一件很重的活"。
独立上下文:把噪音挡在主对话之外
想象你让主 agent"把这个陌生代码库摸一遍,告诉我认证是怎么实现的"。它要读一堆文件、跑一堆搜索,这些中间过程全堆进主对话窗口,等摸清楚了,窗口也塞满了探索垃圾,真正的任务反而没空间了。
子Agent的思路:派一个有独立上下文的分身去干重活,它在自己的窗口里折腾,干完只交回一句精简结论:
sub = Agent( llm=parent.llm, # 复用同一个模型连接,费用并入主会话 tools=[t for t in parent.tools if t.name != "agent"], # 禁递归 max_rounds=20, # 轮次上限比主 agent 更紧 ) result = sub.chat(task)几个关键的约束设计:
- 上下文隔离:子Agent拥有全新的
messages列表,主窗口始终干净; - 输出截断:结论超过 5000 字符时截到 4500,免得省下的空间又被超长报告吐回去;
- 失败不外溢:子Agent抛异常只会变成一段文字结果,不会击穿父循环;
- 状态不泄漏:子Agent自己
cd过的目录会在返回时恢复,不会污染父会话的 shell 状态。
禁止递归:分身只能有一层
上面那行# no recursive agents是这段代码里最重要的一句。派生子Agent时,工具集被刻意过滤掉agent本身——子Agent手里没有"开分身"的能力,它干不了的事只能自己硬扛。
为什么一刀切?因为递归 Agent 是一颗随时失控的炸弹:主派子、子派孙……每一层都在烧 token、占线程、加延迟,而模型对"该不该再拆"的判断并不可靠,完全可能陷进越拆越细、永远收不拢的无底洞。禁掉递归,要么子Agent自己搞定,要么失败返回,没有第三种走向。
很多人以为子Agent是为了"并行干更多活",其实它最大的价值是上下文管理:把别的活的噪音挡在主对话之外,其次才是任务分解。
快速上手:跑起来看看
想亲手体验,五分钟就能跑起来:
git clone https://gitcode.com/gh_mirrors/co/CoreCoder cd CoreCoder pip install -e .配上OPENAI_API_KEY(或 DeepSeek、本地 Ollama 等两个环境变量),然后:
corecoder -p "看看这三个文件分别写了什么:a.py b.py c.py"注意观察:三个独立的read_file调用会几乎同时出现在终端里——那就是线程池在并发工作。
总结:从这两行设计看懂并发工程
| 机制 | 一句话 | 关键文件 |
|---|---|---|
| 线程池并发 | 多个独立工具调用同时跑,IO 密集场景几行代码搞定 | agent.py |
| 线程本地 cwd | 用threading.local消灭竞态 | tools/bash.py |
| 文件写入锁 | 并发批次里写操作串行落地 | tools/base.py |
| 前置结算 | 权限与 hooks 在主线程完成,避免多 prompt 混排 | agent.py |
| 子Agent | 独立上下文 + 禁递归 + 输出截断 | tools/agent.py |
- 模型一次返回多个独立工具调用时,线程池并发是笔划算的买卖,IO 密集场景下它是最省事的并发原语;
- 并行不是免费的:它给每个带可变状态的工具都加了一道并发正确性要求,加并行前先问自己"扛不扛得住被同时调用";
- 子Agent首先是上下文管理手段,其次才是任务分解手段;禁止递归是用"一刀切"换"绝不失控"。
更多细节,项目里自带的双语源码解读系列第五篇(article/05-parallel-and-subagents.md)逐行拆解了这两块机制,值得一读。
【免费下载链接】CoreCoder
Minimal AI coding agent (~1,000 lines of Python) inspired by Claude Code. Works with any LLM. Think NanoGPT for coding agents. Formerly NanoCoder.
相关推荐
DuckDB并行查询执行:多线程如何加速复杂分析任务
DuckDB并行查询执行:多线程如何加速复杂分析任务 你是否经常遇到数据分析任务耗时过长的问题?当处理百万甚至上亿行数据时,单线程执行往往需要等待数分钟甚至更长
数据库OLAP嵌入式数据库数据分析Forge Task 工具深度解析:多 Agent 并行委派、子代理执行与会话恢复机制
Forge Task 工具深度解析:多 Agent 并行委派、子代理执行与会话恢复机制 导读 Forge 的 task 工具是面向复杂多步任务设计的"代理型工具
人工智能AI Agent代码智能体AI 应用CLI开发工具Anomalib 管道并行执行:ParallelRunner 进程池机制与多 GPU 任务调度实践
Anomalib 管道并行执行:ParallelRunner 进程池机制与多 GPU 任务调度实践 本篇指南聚焦 Anomalib 管道(pipeline)框架
人工智能计算机视觉深度学习模型评测
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考