拆解 harness9(11):M1 总结与 M2 展望

发布时间:2026/7/30 9:36:15

拆解 harness9(11):M1 总结与 M2 展望 M1 完成之后harness9 为什么要走向本地 Agent OS关于 harness9harness9 是一款 Local-First、轻量级、功能完备、生产可用的通用 Go Agent 框架。官网https://zhangshenao.github.io/harness9/zh/GitHubhttps://github.com/ZhangShenao/harness9⭐ Star 是对开源工作最直接的支持欢迎提 Issue 和 PR。TL;DRMilestone-1 终于完成了harness9 现在不只是“能跑一下”的 Agent而是有执行、边界、记忆、隔离和检查的一套本地底座。对于增加系统复杂度这件事我们一直相当克制同一套核心逻辑服务命令行和流式界面少一份重复就少一份以后难查的差异。Agent 能做什么、这次能不能做、问题出在哪里是三件不同的事。M1 把它们分开处理。Sub-Agent、Planning、Context Engineering 和 Memory 等基础能力已经 readyM2 要补上的是让多个 Agent 能一起干活、暂停后接得上、过程也说得清的那层协调机制。M2 已经开始搭建 Mission Foundation。它不会急着做一个“什么都能调度”的大平台而是先把本地开发任务的计划、审批、执行、交接和验收走稳。这一路花了很久也投入了很多心血。走到 M1 这个里程碑不是终点而是终于有了值得继续往前走的地基本文你将学到你将看懂一个 Agent 从“想一想”到“真的动手”时harness9 怎样让它不乱跑。你将理解为什么权限、隔离和工具不该混成一句提示词里的要求。你将分清 M1 已经交付的能力和 M2 正在补齐的协作能力。你将看到我们怎样用测试和记录来判断“真的做好了”而不是只看模型说得像不像。你将知道 M2 想做什么也知道它刻意不做什么。M1 我们做了哪些工作GitHub M1 已完成harness9 v1.0.0也已经在 2026-07-21 发布。看到它关闭的那一刻心里很安静也很踏实。这个项目走了很久。一路上我们不是只在给 Agent 加功能而是在反复追问它真要动手时能不能看得见、拦得住、出了问题能不能找到原因很多看上去不起眼的细节背后都是一次次推翻、补测试、再收紧边界。M1 的完成意味着这些问题终于有了一套经得住使用的回答。所以M1 不等于“接上一个模型”。它交付的是一个 Agent 在自己电脑上可以被认真对待的底座能运行也有规则能做事也留得下记录。M2 才是在这个底座上让多个 Agent 学会配合。这张图不能代替代码但它能帮人一眼看懂 M1 的分工。AgentEngine在中间却不是一个人扛下所有事。Planning 负责把事情列清楚Sub-Agent 帮忙拆活Memory 不让信息只留在一次聊天里Sandbox 和权限链负责守边界Observability 与 Evals 则负责留下“发生过什么、做得对不对”的记录。最容易走偏的做法是把所有事情都塞进 Agent 的主循环。这样一开始省事后面每加一个功能都可能牵一发动全身。harness9 选了更慢、但更稳的路让这些能力围着核心接入而不是挤进核心。EngineObserver就是一个小例子——它只在一次对话和每一轮开始、结束时接住信息不去打扰核心工作。typeEngineObserverinterface{OnInteractionStart(ctx context.Context,sessionID,promptstring)context.ContextOnInteractionEnd(ctx context.Context,turnsint,errerror)OnTurnStart(ctx context.Context,turnint)context.ContextOnTurnEnd(ctx context.Context,turnint,hasToolCallsbool)}这份约定让默认的noopObserver几乎不用额外成本也让 OpenTelemetry 能把一次对话里模型和工具的过程串起来。说白了M1 的一个坚持是内核要尽量稳定想加的新功能要有边界能看得见、查得清。Agent 的本质还是 ReAct LoopAgent 的工作方式其实不难理解它先看看已有的信息和能用的工具再决定回答或者调用工具继续做事工具的结果回来后它再接着想。M1 把这个来回过程收在同一个runLoop里。能同时做的事就不必硬排队。但“同时”不等于放任。每个工具都有时间限制整个过程也有轮数上限结果回来时依旧按稳定顺序放回去。我们希望它跑得更快也希望出现问题时有清晰的排查链路。看下面这段runLoop。每一轮开始时它会拿到这次真正允许用的工具。这里不是“拜托模型别乱用”的口头提醒而是程序只把允许的工具交给模型。在规划阶段写文件这类工具会被挡在外面。availableTools:e.registry.GetAvailableTools()ifplanModeplanning.PlanModePlan{availableToolsfilterReadOnlyTools(availableTools)}msgTokensBefore:memory.EstimateTokens(contextHistory)compactedHistory:e.applyCompactionWith(comp,contextHistory)msgTokensAfter:memory.EstimateTokens(compactedHistory)em.tokenUpdate(msgTokensAftertoolTokens,e.contextWindow)命令行里的Run和界面里一边生成一边显示的RunStream表面上是两种体验但底下没有各写一套循环。它们只是把结果用不同方式交给用户。这样一来轮数、压缩、审批和工具调用都只有一份规则。以后修复一个问题两边一起生效不会出现“命令行没事界面又不一样”的尴尬。工具失败也不急着把整件事掐断。harness9 会把ToolResult{IsError: true}交回给模型让它知道哪里没走通它可以换个参数、换条路或者坦白告诉用户限制在哪。所谓“自愈”关键是别把错误吞掉错误也是关键的 Context它能指导 Agent 的下一步工作。Prompt 不靠谱约束 Agent 要靠权限与 Guardrail本地 Agent 真正让人担心的不是它会不会写几句话而是它会不会执行命令、改文件、访问网络。只靠一句“请小心操作”并不可靠。M1 把这件事拆开来做它有什么能力、这一次是否被允许、就算出错又会被关在哪里分别处理。Tool Registry 决定 Agent 看到且能调用哪些BaseTool。Hook Human-in-the-Loop 决定某次调用是allow、deny还是ask。Sandbox 通过Environment将 bash 和文件操作路由到本地进程或 Docker 容器。Environment只负责几件很实际的事跑命令、读文件、写文件、关掉环境。工具不用自己分辨是在电脑上跑还是在 Docker 容器里跑。打开 Docker 后bash会进容器执行文件依旧指向同一个工作目录不打开时一切照旧走LocalEnvironment。新能力不能把旧用法弄坏这也是我们一直守住的底线。typeEnvironmentinterface{RunBash(ctx context.Context,cmd,workDirstring)(string,error)ReadFile(ctx context.Context,pathstring)([]byte,error)WriteFile(ctx context.Context,pathstring,data[]byte)errorID()stringClose(ctx context.Context)error}Sub-Agent 也不是看见一句“不要继续派人”就会老实。ResolveTools会直接收紧它能用的工具并且拿掉task。这样 Sub-Agent 就不能再递归委派也不能靠几句输入把权限要回来。这个道理很朴素真正值得信任的不是 Agent 说自己会克制而是程序在该拒绝、该暂停、该找人确认的时候真的能把它拦住。状态有了协调还没有M1 已经把很多“别只靠脑子记”的事情落到了本地。SQLite Session 会保存会话和待办TodoStore知道一件事是在等着做、正在做还是已经完成长期记忆会把跨会话的信息留下来后台 Sub-Agent 的进度和结果则由TaskTracker先接住等主 Agent 接着处理。不过我们也不想夸大 M1 的工作。TaskTracker现在负责的是当前进程里的后台任务它会保护好进度完成后让主 Agent 拿一次结果。它已经把当下的并发和重复消息管住了但还不是那种电脑重启后整张任务关系、谁该重试、谁在占用工作区都能自动接上的系统。func(t*TaskTracker)DrainCompleted()[]CompletedTask{t.mu.Lock()defert.mu.Unlock()varout[]CompletedTaskfor_,task:ranget.tasks{iftask.state!TaskRunning!task.injected{task.injectedtrueoutappend(out,CompletedTask{TaskID:task.id,AgentName:task.agentName,FinalText:task.finalText,IsError:task.isError})}}returnout}这不是 M1 少做了什么而是我们给 M2 留下的起点。M1 先把会话、计划、记忆、后台工作和隔离 Agent 各自站稳。M2 再把它们接成一条完整的路谁负责哪件事谁能改哪个工作区前面的事没做完后面的事怎么等电脑中断后又从哪里继续以及恢复时会不会绕开原来的权限。Talk is cheap, show me the trajectoryAgent 很容易给人一种“看起来挺会做”的感觉。但真正做过项目的人都知道能说出来和能稳定做到中间差得很远。M1 因此把过程记录和自动检查放在开发当中而不是等出问题了才去翻日志。OTELEngineObserver会把一次对话和每一轮的过程串起来Provider 会留下模型请求和 tokenTool Hook 会记录工具真的做了什么。另一边EvalHarness用固定脚本去检查该不该调用工具、最后说了什么、有没有报错、绕了多少轮。该失败的就明确失败只是提醒的就给提醒。我们不把所有信号揉成一个看上去漂亮、却说不清原因的分数。到了 M2这件事只会更重要。多个 Agent 一起干活可能卡在前后依赖也可能碰到不同工作区和中断恢复。只看最后一句“我做完了”根本不知道问题出在安排、权限还是某个 Agent 越了界。所以 M2 的每一项工作都要继续把边界、权限、过程记录和验收方式写清楚。目标展望M2 要做什么harness9 M2 的目标叫“本地 Agent OS”。听起来很大但我们想做的事情其实很具体可以在本地批量调度一系列 Agent让它们协作完成复杂的长程任务并且整个过程可审批、可监控、可管理、可暂停也能在中断后有据可查地继续。这件事已经开始。现在正在推进的是 Mission Foundation先把任务、计划、审批、工作区和验收结果这些最容易混乱的东西放到同一套本地记录里。一部分基础状态和本地记录已经落到代码接下来还要把变更审批、调度和端到端协作继续做实。我们宁愿把这层地基打实也不急着把“多 Agent”做成热闹的表演。M2 结果面M1 已有基础M2 正在补齐多 Agent 一起干活SubAgentDefinition、Runner、前后台执行、独立 Session明确能做什么、何时取消或重试、中断后怎么接上任务怎么协作TaskTracker、Todo 状态、工具 Hook谁先做、谁后做、消息怎么交接、多人改文件时怎么避免撞车本地工作区workDir、worktree、Sandbox、Session工作区怎么分配和回收产物和过程怎么留下来记忆与知识LTM、FTS5、MEMORY.md物化视图项目、用户和 Agent 的信息怎么区分、更新和由用户掌控用结果说话Evals、CI gate、OTEL trace更完整的回归检查以及出了问题能回溯到哪一步看得见、管得住TUI、CLI、审批、Sandbox/MCP 状态栏在一个地方查看 Agent、任务、成本、过程和长任务状态顺序比速度更重要。先让所有人都能知道“现在到底发生了什么”再谈开多少个 Agent 一起跑。没有可靠的任务记录、明确的负责范围和取消规则多开几个后台 Agent只会把偶发问题放大成更难复现的麻烦。权限也是一样。子任务可以少拿权限不能多拿系统从中断里恢复也不能偷偷绕过原本需要人确认的操作。这些听上去不炫但它们决定了这套系统是不是能让人放心交给它。M2 也提前说清楚了哪些事不做不去建设云端的大平台不让外部账号在没人确认的情况下自动行动不喊口号要取代操作系统也不做酷炫的 GUI 交互。把“不做什么”讲明白才能把有限的心力留给最重要的事在开发者自己的机器上做出一套可信、看得见、能回头检查的协作方式。结语M1 终于达成了。它不是一张漂亮的功能清单而是很多次认真取舍、很多个边角补齐之后交到我们手里的一个阶段性答案。我们知道M2 会更难。当多个 Agent 同时行动、暂停、失败、再恢复时开发者还能不能一直看清、控制并信任系统这就是接下来要继续回答的问题。但今天也值得停下来认真记住这个时刻harness9 已经走过了最开始那段只有想法、没有底气的路。谢谢每一份投入的心血。M1 是一个里程碑也是继续往前的起点(PS. 一个人写代码还是蛮无聊的如果大家对 harness9 这个项目感兴趣欢迎一起来共建呀~)

相关新闻