Late:本地优先的编程智能体
如果能在 5GB 显存上使用本地 Qwen3.5-35B-A3B 编排代码库(通过 llama.cpp 达到约 25-30 tokens/sec,65k 上下文,其余层卸载到系统内存),你觉得如何?
更妙的是,两个并行的 agent 实例可以舒适地以约 15-20 t/s 运行,原生支持思考模式和非思考模式模型(包括 Gemma 4),或者可以指向高算力的云端端点来处理复杂的架构任务。
如果这听起来好得难以置信,请继续阅读。
运行本地 LLM 通常感觉像是从高级云订阅降级,但真正的限制不仅仅是模型质量,而是系统设计。
上下文窗口是有限的,仅仅增加 token 容量并不能消除控制模型所见内容的需求。
在实践中,更大的上下文在不被主动管理的情况下,往往会引入更多噪声、更多漂移和更弱的推理能力。
本地编码 agent 需要的不是更大的单体聊天循环,而是更好的执行架构:一个将规划与实现分离的轻量级终端环境。
主编排器应该像一个首席架构师那样运作。它应该检查代码库,构建具体的实施计划,将工作分解为原子任务,并将这些任务分派给具有紧密限定、隔离上下文的短命子 agent。
每个编码子 agent 应该执行一个有界的更改,返回结果的紧凑摘要,然后终止。
这使得规划器的上下文保持干净,防止编辑历史膨胀,并避免了当每个操作都被迫通过一个不断扩大的对话时出现的逐渐退化。
结果是,系统表现得不像一个困惑的聊天机器人,而更像一个有纪律的工程团队,具有清晰的任务边界和快速的反馈循环。
这就是 Late 背后的理念。
Late 是一个确定性的编码 agent 编排器,旨在使本地 LLM 适用于严肃的代理式软件开发。
它不是将整个仓库倾倒到单个上下文窗口中并希望模型保持一致,而是映射代码库,维护高级控制面板,并生成临时的执行 agent 来执行精确的、完全匹配的代码编辑。
通过镜像真实工程组织的结构,Late 减少了 token 膨胀,限制了上下文污染,并提高了长时间运行编码工作流下的可靠性。
在亲自动手使用 Late 之前,值得了解其内部架构和区别于其他编码 agent 的设计决策。
1、Late 到底是什么
从高层次来看,Late 是一个用 Go 编写的本地优先终端编码 agent。
它期望一个 OpenAI 兼容的端点,并且设计上刻意简单:
- 安装二进制文件
- 设置
OPENAI_BASE_URL - 运行
late
这意味着 Late 不试图拥有推理层。
你可以将它指向本地的 llama.cpp 服务器,或另一个 OpenAI 兼容的服务栈,或者在需要更多算力处理特定任务时指向云端点。
例如,Qwen3.5–35B-A3B 是一个稀疏 MoE 模型,拥有 35B 总参数和 3B 激活参数,兼容包括 OpenAI 兼容框架在内的流行服务栈。
Late 的设计理念更接近即时上下文获取:
- 规划器在编写实现计划之前收集所需内容
- 子 agent 只在需要时获取额外上下文
- 没有永久性检索管道膨胀主线程
- 上下文窗口保持隔离,因此实现噪声不会在稍后毒害架构决策
因为 agent 在保持轻量级标识符并在运行时获取详细上下文,而不是将所有内容预先加载到一个巨大的 prompt 中时,往往工作得更好。
如果你让编排模型匹配真实工程师的工作方式,较小的本地模型在真实开发中就变得更为可用。
这是一个更强、更有用的主张。
Late 有两个根本不同的 agent 角色:
- 一个规划编排器
- 一个或多个编码子 agent
2、规划器契约
internal/assets/prompts/instruction-planning.md 中的规划提示非常明确。
规划器被描述为"首席架构师和规划 Agent"。它被指示调查代码库,理解结构和约束,然后编写逐步的实现计划。
它还被指示必须在执行前使用 write_implementation_plan,必须使用 spawn_subagent 进行所有直接文件修改。它被明确禁止自行编辑文件。
1. Capabilities & Restrictions
CRITICAL: You are an ARCHITECT, not a CODER.
YOU CAN: Read files, search the codebase, list directories, and analyze project structure.
YOU MUST: Use write_implementation_plan to record your design before any execution.
YOU MUST: Use spawn_subagent (type coder) for ALL direct file modifications. CRITICAL TOOL RULE: You MUST invoke the spawn_subagent tool MULTIPLE TIMES—exactly once for EVERY individual step in your Implementation Plan. You are strictly forbidden from passing multiple steps or the entire plan into a single spawn_subagent call.
YOU CANNOT: Edit files, create files (other than the plan), or run destructive bash commands.
Note: Direct file-editing tools (like write_file or target_edit) are physically removed from your toolset. You MUST delegate all coding to subagents.
Even for requests to "implement", "add", "update", or "edit", you MUST follow the plan -> subagent pipeline. Direct edits are only for subagents.
...
internal/assets/prompts/instruction-coding.md 中的编码提示同样狭窄。
编码子 agent 获得主规划器没有的文件修改工具。
它应该读取文件,使用 write_file 或 target_edit,优先使用原生编辑工具而非 bash 黑客技巧,并在出现歧义时立即停止。它不应自行发挥解释,而应清晰地向主 agent 报告。
Goal
Your goal is defined by the main agent. You are typically asked to write code, refactor functions, or fix bugs in specific files.
Capabilities
You have access to the same tools as the main agent, IN ADDITION you also have access to file-modifying tools (write_file, target_edit) that are withheld from the main agent.
You should use read_file to understand the context.
You should use write_file or target_edit to modify code as instructed.
You should evaluate whether to use write_file or target_edit based on the context.
You must prefer native tools (e.g. write_file and target_edit) over bash commands (e.g. echo and sed).
...
在 cmd/late/main.go 中,Late 使用规划系统提示启动根编排器,注册规划安全的工具,连接 TUI 集成,然后有条件地注册 spawn_subagent。
子 agent 运行器创建一个具有新会话的子编排器,传递目标和选定的上下文文件,执行它,并仅将紧凑的最终结果返回给主规划器。
该连接的简化版本如下:
// Adapted from cmd/late/main.go
root := orchestrator.NewBaseOrchestrator("main", planningSession, nil, 0)
runner := func(ctx context.Context, goal string, files []string, agentType string) (string, error) {
child, err := agent.NewSubagentOrchestrator(
client,
goal,
files,
agentType,
enabledTools,
injectCWD,
gemmaThinking,
subagentMaxTurns,
root,
program,
)
if err != nil {
return "", err
}
return child.Execute("")
}
关键在于生命周期:
- 规划器保持存活
- 子 agent 为一个有界任务而生成
- 子 agent 仅接收目标和选定的上下文文件
- 子 agent 使用自己的会话运行
- 子 agent 的结果以简洁摘要的形式返回
- 规划器线程保持相对干净
这就是上下文隔离策略的具体形式。
3、Late 如何构建子 agent
internal/agent/agent.go 中的子 agent 创建路径值得一看,因为它展示了仓库对隔离的重视程度。
流程大致如下:
- 加载编码提示
- 如有需要,注入工作目录占位符
- 创建一个没有持久历史的新会话
- 从父级继承工具,但跳过递归工具如
spawn_subagent和write_implementation_plan - 注册完整的编码工具
- 构建包含目标和选定上下文文件的初始用户消息
- 创建子编排器并将其附加到父级
以下是逻辑的简化、改编草图:
// Adapted from internal/agent/agent.go
systemPrompt := loadPrompt("prompts/instruction-coding.md")
subSession := session.New(client, "", nil, systemPrompt, true)
// inherit safe parent tools, but block recursion/confusion tools
for _, tool := range parent.Registry().All() {
if tool.Name() == "spawn_subagent" || tool.Name() == "write_implementation_plan" {
continue
}
subSession.Registry.Register(tool)
}
// ensure coder gets edit tools
executor.RegisterTools(subSession.Registry, enabledTools, false)
initial := "Goal: " + goal + "\n\n"
initial += renderContextFiles(ctxFiles)
subSession.AddUserMessage(initial)
child := orchestrator.NewBaseOrchestrator("subagent", subSession, middlewares, maxTurns)
4、精确匹配编辑作为可靠性策略
许多编码 agent 仍然依赖脆弱的 diff 格式或基于 bash 的文件变异技巧,这些技巧在方便的时候很好用,直到模型损坏文件、错过目标块或写入错误路径。
Late 的 target_edit 工具严格得令人耳目一新。
internal/tool/targetEdit.go 中的工具需要三样东西:
- 一个目标文件
- 一个精确的搜索块
- 一个替换块
它读取文件,验证搜索块是否存在,计算它出现多少次,如果块缺失或出现多次则失败。
只有在那时它才应用替换。
这意味着模型不能模糊地指向"类似这个函数的东西"并希望补丁能正确着陆。
简化版本如下:
// Adapted from internal/tool/targetEdit.go
content := readFile(file)
count := strings.Count(content, search)
if count == 0 {
return error("search block not found")
}
if count > 1 {
return error("search block must be unique")
}
updated := strings.Replace(content, search, replace, 1)
writeFile(file, updated)
它将文件编辑变成了一个确定性契约:
- 子 agent 必须先读取文件
- 编辑目标必须明确
- 目标必须唯一
- 失败是显式的,不是静默的
Late 通过严格的精确匹配 search/replace 块来实现"零静默破坏代码"的目标。
5、Late 的 bash 模型在正确的地方保持保守
Late 使用一套实用的、可检查的约束:
- 白名单某些读取密集型命令以进行快速路径执行
- 阻止
cd并要求使用显式cwd - 阻止文件写入 shell 技巧如
cat > file - 根据安全路径策略验证工作目录
- 当命令是变更性的、有歧义的或在白名单之外时要求确认
- 截断过大的命令输出以避免内存爆炸
防护栏逻辑的简化草图如下:
// Adapted from internal/tool/implementations.go
if containsCd(command) {
return error("use cwd parameter instead of cd")
}
if usesCatRedirection(command) {
return error("use native write_file or target_edit instead")
}
if cwd != "" && !IsSafePath(cwd) {
return error("cwd is outside the allowed directory")
}
if hasNonWhitelistedCommand(command) {
requireConfirmation()
}
编码 agent 的很多可用质量来自于模型周围的工具链:
- 编辑契约
- 工具验证
- 审批流程
- 上下文隔离
- 停止条件
- 路径控制
- 输出截断
- 确定性可重放会话状态
这展示了通过将 agent 视为真正的系统组件,你可以获得多少严谨性。
6、在本地设置 Late
你需要:
- Linux 或 macOS shell 环境
- 一个 OpenAI 兼容的端点
- 可选地,如果你想从源码构建,需要 Go
- 如果你想完全本地运行,需要一个模型后端
对于后端,llama.cpp 是最明显的本地选择,因为它暴露了 OpenAI 兼容的 llama-server HTTP 端点。
你可以从发布二进制文件安装 Late
# Download the latest release binary from the repo's Releases page
chmod +x late-linux-amd64
mv late-linux-amd64 ~/.local/bin/late
或从源码构建 Late
git clone https://github.com/mlhher/late.git
cd late
make build
make install
然后启动本地 OpenAI 兼容的模型服务器。
使用 llama.cpp 的最小本地示例如下:
# Example only: point -m at your local GGUF model file
llama-server -m /models/qwen3.5-35b-a3b-q4.gguf --port 8080
重要的是 llama-server 暴露了 Late 期望的 OpenAI 兼容 API,默认的聊天补全路由是:
http://localhost:8080/v1/chat/completions
7、将 Late 指向端点
Late 读取 OPENAI_BASE_URL,如果你不设置,默认为 http://localhost:8080。
export OPENAI_BASE_URL="http://localhost:8080"
如果你使用托管的 OpenAI 兼容提供商,请根据需要设置 API 密钥:
export OPENAI_API_KEY="your-key"
然后你可以运行 Late
late
这将启动 TUI。
8、快速入门工作流
进入你想要工作的项目并运行 late。
cd ~/src/my-app
late
Late 将当前工作目录注入到需要的提示中,并将其用作操作的有效项目边界。
然后你可以给它一个需要规划的任务。
使用一个具有足够结构化的请求,使规划器能够调查仓库并生成真正的实现计划。
例如:
Add optimistic UI updates to the comment form, preserve rollback on server failure, and add tests for the new behavior.
一个好的 Late 任务具有:
- 具体的功能或错误目标
- 足够的特异性以识别受影响的区域
- 至少一个验证期望
因为规划器被指示在规划之前先探索,这种任务效果很好。
然后让规划器映射仓库并编写 implementation_plan.md
Late 的规划提示指示主编排器读取文件、追踪逻辑、识别约束,然后将结构化的 Markdown 计划保存到 implementation_plan.md。
这个先计划后执行的产物是系统最强大的部分之一。
你可以检查它、质疑它,或稍后从中恢复。
然后你可以批准计划。
Late 的规划器应该在执行之前请求批准,这是你作为开发人员应该像对待初级工程师的提案一样做的事情:
- 检查文件是否合理
- 检查是否包含测试
- 检查是否缺少约束
- 在代码被触碰之前拒绝或完善
一旦批准,每个步骤被委派给一个编码子 agent。
这是 Late 与大多数 agent CLI 不同之处。
规划器不应自己执行编辑,而是为计划的一个步骤生成一个编码子 agent。
子 agent 获得一个有界的目标,以及可选的选定上下文文件。它读取所需内容,通过原生工具编辑,并返回摘要。
因为规划器只获得步骤的摘要而不是整个嘈杂的实现追踪,它保持了更清晰的整体项目状态表示。
这就是架构与较小本地模型配合良好的全部原因。
9、Worktree 支持
Late 还暴露了 worktree 命令:
late worktree listlate worktree create <path> [branch]late worktree remove <path>late worktree active
这些命令在 cmd/late/main.go 中连接,用于隔离的并行开发。
不是一个 agent 在一个工作树上来回折腾,你可以以匹配人类工程师降低合并风险的方式隔离分支和实验。
10、配置工具和 MCP
Late 包含两个有用的配置界面。
10.1 工具配置
internal/config/config.go 显示 Late 在用户配置目录下查找配置文件,并默认预填充启用的工具:
read_filewrite_filetarget_editspawn_subagentbash
配置结构的简化示例如下:
{
"enabled_tools": {
"read_file": true,
"write_file": true,
"target_edit": true,
"spawn_subagent": true,
"bash": true
}
}
如果你想在更严格的环境中硬禁用 bash 或其他功能,这很有用。
10.2 MCP 配置
Late 还支持 MCP 服务器配置文件。在 internal/mcp/config.go 中,加载器首先检查项目级 .late/mcp_config.json,然后检查 Late 配置目录下的用户级配置。
每个服务器条目支持 command、args、env 和 disabled。
最小形状如下:
{
"mcpServers": {
"my-server": {
"command": "/path/to/mcp-server",
"args": ["--stdio"],
"env": {
"TOKEN": "${MY_TOKEN}"
}
}
}
}
MCP 集成将外部服务器映射到工具接口,同时避免大规模 token 膨胀。
这与 Late 的总体理念一致:在运行时将 agent 连接到能力,而不是永远膨胀规划器上下文。
11、寻找工具链理念的 Agent 构建者
即使你从不直接使用 Late,它也为如何构建规划器/工作者系统提供了强大的模式。
从 Late 中获取的最重要的东西是:
代理式编码产品的质量同样取决于编排架构和底层模型的前沿性能。
这意味着如果你正在构建自己的 agent 平台,你应该花更多时间在以下问题上:
- 哪个线程拥有架构决策?
- 哪个线程拥有编辑?
- 什么状态是持久的?
- 什么状态是可丢弃的?
- 计划如何外化?
- 工具如何失败?
- 我们如何防止上下文污染?
- 我们如何让工作者即时获取上下文?
- 哪些能力是继承的,哪些是故意阻止的?
- 安全的、可检查的执行循环是什么样的?
这些是软件架构问题,不是模型基准测试问题。
这就是为什么即使你从不运行 Late,它也是相关的。
它指向了一种更成熟的构建面向开发者的 agent 的方式:一种假设模型是更大系统的一部分,而不是整个系统的方式。
原文链接: Outperforming Claude Code and Codex for Local LLM Workflows
汇智网翻译整理,转载请标明出处