最近开源圈又冒出一个 Agent 框架——OpenJarvis,出自斯坦福 Scaling Intelligence Lab 和 Hazy Research(就是 Christopher Ré 那个组),2026 年 3 月 12 日以 Apache 2.0 协议放出,作者列表里还挂着 Azalia Mirhoseini。和满天飞的 Agent 框架不同,它的口号是Personal AI, On Personal Devices,目标很直白:让个人 AI Agent 默认跑在你自己的设备上,只在真正必要时才调用云端。支撑这个口号的是它们之前的 Intelligence Per Watt 研究——本地语言模型已经能处理约 88.7% 的单轮对话/推理查询,且 2023 到 2025 年每瓦智能提升了 5.3 倍。一句话:模型和硬件都准备好了,缺的是把本地优先落地的软件栈。OpenJarvis 想做的,用它自己的话说,是本地 AI 时代的 PyTorch。这篇文章咱不复述官方介绍,而是把仓库 clone 下来,从源码角度拆它的几个核心设计:可插拔注册表、trace 驱动的本地/云路由闭环、把能耗写进数据模型的遥测层、四种 Agent 循环原型,以及它的 skill 机制与 Hermes 的本质区别。仓库整体是 Python(82.7%) Rust(8.7%) TypeScript(7.3%),核心逻辑在src/openjarvis/。一、五原语 一个注册表:可插拔的全部秘密先纠正一个流传的说法。外面文章爱说五原语,README 又说三个核心思想,其实对着代码看,真正的骨架是五个可插拔抽象 八条设计原则。而且它自己文档里都没完全统一:架构文档一处写Intelligence / Engine / Agentic Logic / Memory / Learning,另一处写Intelligence / Engine / Agents / Tools / Learning。对照core/registry.py的实际注册表,真相是 Memory 并非独立原语,而是挂在 Tools 之下的一种后端。所以规范的五原语是:Intelligence:模型目录,记录模型能力、参数量、定价(ModelRegistry)Engine:推理后端,抽象基类InferenceEngine,实现有 Ollama / vLLM / SGLang / llama.cpp / CloudAgent:推理循环,抽象基类BaseAgentTools(含 Memory):工具与记忆后端,BaseTool/MemoryBackend(SQLite / FAISS / ColBERT / BM25 / Hybrid)Learning:路由策略,抽象基类RouterPolicy(Heuristic / Learned / GRPO),横切其余四者让一切可插拔成立的,是一个泛型注册表加装饰器。核心就一个类RegistryBase[T],精髓在按子类名隔离存储:classRegistryBase(Generic[T]):classmethoddef_entries(cls)-Dict[str,T]:attr_namef_registry_entries_{cls.__name__}# 关键:按子类名隔离storagegetattr(cls,attr_name,None)ifstorageisNone:storage{}setattr(cls,attr_name,storage)returnstorageclassmethoddefregister(cls,key:str):defdecorator(entry):ifkeyincls._entries():raiseValueError(...)# 重复注册直接报错cls._entries()[key]entryreturnentryreturndecoratorf_registry_entries_{cls.__name__}这一行把每个子类的存储隔离开,于是EngineRegistry、AgentRegistry、RouterPolicyRegistry各自一套字典,注册时绝不串台。加一个新引擎只需要:EngineRegistry.register(my-engine)classMyEngine(InferenceEngine):defgenerate(self,messages,*,model,**kwargs):...单文件改动,没有中央工厂、没有 YAML、不用改任何已有代码,import 即注册。整个仓库有十几个这样的注册表(连后面提到的挖矿模块都有一个MinerRegistry),全部共享这一套机制。这就是设计原则里Pluggable Everything在实现层面的全部内容。二、Learning 闭环:本地还是云,是学出来的这是 OpenJarvis 区别于 LangChain / CrewAI 的真正内核——别家默认云推理,它把用哪个模型做成一个可学习的策略。拆成四个零件。① 复杂度评分器(learning/routing/complexity.py):纯正则,零模型开销,给 query 打 0~1 分。score0.0score0.20*length_score# 长度信号占 20%ifhas_code:domain_score0.7# 代码/数学信号占 25%# reasoning / multi-step / 问号数 / 子任务数 ... 加权汇总分数再映射到 token 预算档位(trivial1024 一路到 very_complex16384),思考型模型(qwen3.5 / r1 / o1 等)预算翻倍留 CoT 空间。整个过程不调用任何 LLM,所以路由决策本身几乎不耗算力。② 启发式路由器(learning/routing/router.py):六条规则按序短路。defselect_model(self,context):ifcontext.urgency0.8:# 规则5:急 → 最小模型(覆盖一切)return_smallest_model(available)ifcontext.has_code:# 规则1:代码 → 找名字带 code/coder 的return_find_model_by_tag(available,code)or_largest_model(...)ifcontext.has_math:# 规则2:数学 → 最大模型return_largest_model(available)ifcontext.complexity_score0.20:# 规则3:简单 → 小模型(省电)return_smallest_model(available)ifcontext.complexity_score0.55orcontext.has_reasoning:# 规则4:难 → 大模型return_largest_model(available)returnself._default# 规则6:兜底注意规则 5 把紧急度凌驾于一切之上——宁可用小模型快速本地出结果,也不甩给云。这正是本地优先在代码里的体现。③ 学习型路由器(learning/routing/learned_router.py):真正自我改进的部分。它不靠手写规则,而是从历史 trace 里学query_class → 最佳模型的映射,并且带一个置信门槛——样本不够就退回兜底。defselect_model(self,context):query_classclassify_query(context.query)if(query_classinself._policy_mapandself._confidence.get(query_class,0)self.min_samples):# 门槛默认 5 条样本returnself._policy_map[query_class]returnself._default策略表怎么更新?把所有 trace 按 query_class 分组,每个模型算一个复合分,取最高分写进策略表:defcomposite_score(self):srself.successes/self.count# 成功率fbself.feedback_sum/self.feedback_count# 用户反馈return0.6*sr0.4*fb# 6:4 加权④ 奖励函数(learning/routing/heuristic_reward.py):这里能耗/成本意识直接写进了公式。latency_scoremax(0,1-latency/max_latency)cost_scoremax(0,1-cost/max_cost)efficiency_scorecompletion_tokens/total_tokens reward0.4*latency_score0.3*cost_score0.3*efficiency_score延迟 0.4、成本 0.3、token 效率 0.3——把快、省钱、不啰嗦量化成了可优化的标量。四个零件接起来就是一个闭环:每次推理 → 记一条 trace → 分析成败与反馈 → 更新策略表 → 下一条同类 query 走更优模型。这就是它把本地优先从口号变成可优化系统的方式。三、Intelligence-per-Watt:能耗被写进了数据模型这是 OpenJarvis 最不一样的地方。别的框架顶多记延迟,它把能耗当一等公民。每次推理自动落库一条TelemetryRecord,字段里直接有焦耳和瓦特:dataclass(slotsTrue)classTelemetryRecord:latency_seconds:floatttft:float# 首 token 延迟cost_usd:floatenergy_joules:float# ← 焦耳power_watts:float# ← 瓦特...更狠的是它针对不同硬件写了独立的能耗采集器:energy_rapl.py(Intel CPU)、energy_nvidia.py、energy_amd.py、energy_apple.py。NVIDIA 那个不是估算,是直接读硬件计数器——Volta 以上的卡用nvmlDeviceGetTotalEnergyConsumption()读起止毫焦,差值除以 1000 就是焦耳;老卡才退回功率轮询做梯形积分。聚合器(telemetry/aggregator.py)算出来的指标才是论文标题的真身:avg_tokens_per_joule(每焦耳产出多少 token)、avg_energy_per_output_token_joules、avg_throughput_per_watt,甚至把 prefill 和 decode 两个阶段的能耗分开统计。换句话说,哪个模型在你这台机器上每瓦最划算是它能直接 SQL 查出来的数字。这也是它研究平台属性的核心——一切都要能 benchmark。顺带一提,读代码时还翻到一个意外模块mining/和MinerRegistry,它在对接一条叫 Pearl 的 PoUW(Proof-of-Useful-Work)区块链,让你本地跑推理的同时挖矿。验证端是纯 Rust plonky2 的 STARK 证明,硬件中立,只认数学对不对。这块还在早期,算是个挺斯坦福的彩蛋,这里不展开。四、八个 Agent,其实是四种循环原型agents/是仓库最大的模块(约 2 万行),但八个内置 Agent 拆开看本质只有几种推理循环,共享同一个BaseAgent抽象:Agent循环范式默认最大轮数工具调用方式simple单轮1无native_reactThought→Action→Observation10正则解析自由文本native_openhandsCodeAct(写跑 Python)3多格式兼容orchestrator多轮自动选工具—原生 OpenAI tool-callingoperative/monitor_operative常驻/持续20原生 tool-callingclaude_code/opencode/openhands子 Agent 委派—spawn 外部 agent这里有个贯穿全局的工程取舍:ReAct 和 CodeAct 用正则解析自由文本(因为本地小模型常常不支持原生 tool-calling),而 orchestrator / operative 走 OpenAI 原生 tool-calling(模型够强时)。这种双轨设计正是它能在弱本地模型上也跑起来的原因。ReAct 的run()就是一个朴素 for 循环:生成 → 解析 → 判断终止 → 执行工具 → 把 Observation 塞回上下文。for_turninrange(self._max_turns):ifself._loop_guard:messagesself._loop_guard.compress_context(messages)# 防上下文爆炸resultself._generate(messages)parsedself._parse_response(result[content])ifparsed[final_answer]:# 出现 Final Answer → 返回returnAgentResult(contentparsed[final_answer],...)ifnotparsed[action]:# 没有 Action → 当成最终回答returnAgentResult(contentcontent,...)tool_callToolCall(nameparsed[action],argumentsparsed[action_input])tool_resultself._executor.execute(tool_call)observationfObservation:{tool_result.content}messages.append(Message(roleRole.USER,contentobservation))# 喂回模型CodeAct(native_openhands.py)真正值得抄的不是会跑 Python,而是它的_extract_tool_call()同时兼容三种模型输出习惯——因为不同本地模型吐 tool-call 的格式五花八门:# 格式1:Action / Action Input(标准)action_matchre.search(rAction:\s*(.),text,re.IGNORECASE)# 格式2:tool_callname $keyvalue/tool_call(XML 风格)# 还分别兼容 $keyvalue、keyvalue/key、甚至 GLM 模型爱用的 key: value# 代码块直接执行matchre.search(rpython\n(.*?),text,re.DOTALL)这是本地优先被迫长出来的肌肉:云端 API 格式统一,本地一堆开源模型各说各话,框架只能在解析层把脏活全包了。常驻模式operative的核心在于跨会话状态持久化,状态和会话历史都落到 memory backend,用operator:{id}:state这种 key,还带一个兜底——即便模型自己忘了存状态,框架也会自动把回复摘要存下来:def_auto_persist_state(self,content):# agent 没显式存就自动存摘要summarycontent[:1000]self._memory_backend.store(foperator:{self._operator_id}:state,summary)最后是一个容易被忽略但很实用的防呆层loop_guard.py,用两招防 agent 卡死:对(工具名, 参数)做 SHA-256 哈希计数,同一调用重复超过 3 次直接拦截;再用滑动窗口检测 A-B-A-B 这种来回横跳。对要长时间无人值守跑的端侧 agent,这层几乎是必需品。max_identical_calls3# 同一 (tool, args) 重复超 3 次 → 拦ping_pong_window6# 检测 A-B-A-B 来回横跳max_context_messages100# 上下文超 100 条 → 压缩五、Skill 机制:和 Hermes 的本质区别Hermes(Nous Research 出品)是目前社区最火的 Agent,两者经常被拿来比。最有意思的是,我从代码里挖到一个关键差异——它俩号称的skill 自我进化根本不是一回事。先看 OpenJarvis 的 skill 数据模型。它不只是一段文档,而是一条可执行流水线(skills/types.py):dataclass(slotsTrue)classSkillStep:tool_name:str# 调用某个工具skill_name:str# 或者调用另一个 skill(可组合)arguments_template:str{}# Jinja 风格模板output_key:str# 结果存到上下文哪个 keydataclass(slotsTrue)classSkillManifest:name:strsignature:str# Base64 Ed25519 签名(可验真伪)markdown_content:str# 从 SKILL.md 加载的正文两个细节:skill 可以用skill_name调用另一个 skill(组合性);每个 skill 带 Ed25519 签名,因为要从外部仓库导入,得能验证没被篡改。README 那句每个 skill 都是一个 tool在代码里就是SkillTool适配器,tool_id fskill_{name},还能从模板里的{placeholder}自动反推出参数表。于是 agent 在工具目录里看到的 skill 和普通工具长得一模一样,按需调用。skills/sources/下有四个解析器,其中HermesResolver直接 git clone NousResearch/hermes-agent,遍历它的skills/category/skill/SKILL.md目录。能互导的前提是双方都遵循 agentskills.io 标准——所以jarvis skill install hermes:arxiv就能把 Hermes 的技能拿过来,反过来也行。而最关键的区别在自我进化的实现哲学,两者几乎相反:HermesOpenJarvis触发方式完成复杂任务(5 工具调用)后自主写新 skill攒够 trace(默认 ≥20 条)后批量优化已有 skill进化对象生成全新技能文档优化已有技能的描述 few-shot 示例进化时机在线、运行中离线、批处理进化引擎LLM 自己写DSPy / GEPA 优化器是否改原文件直接写/改不改原文件,写 sidecar overlay是否度量效果主要靠 agent 判断benchmark 量化 impact看learning/agents/skill_optimizer.py就明白了:它把 trace 按 skill 名分桶,够 20 条就跑 DSPy 或 GEPA,把优化结果写成一个叠加文件,而不动原版。classSkillOptimizer:def__init__(self,*,min_traces_per_skill20,optimizerdspy):# 或 gepa...defoptimize(self,trace_store,...):bucketsself._bucket_traces_by_skill(traces)# 按 skill 分桶forskill_name,skill_tracesinbuckets.items():iflen(skill_traces)self._min_traces:continue# 样本不够就跳过outputself._run_dspy(...)# 或 _run_gepa(...)# 结果写到 ~/.openjarvis/learning/skills/name/optimized.toml一句话总结这个区别:Hermes 是写新技能的生成式进化,OpenJarvis 是调优老技能的优化式进化。前者更野蛮生长、更像一个有自主性的个体;后者更克制、可度量、可回滚(overlay 不动原文件),骨子里是斯坦福那套一切都要能 benchmark的研究范式。写在最后把这几块拼起来,OpenJarvis 的设计意图就很清楚了:它不是又一个调用云 API 的 Agent 编排器,而是一套以本地执行为默认、把能效当成一等约束、用 trace 反馈持续优化路由与技能的研究型基础设施。如果你做端侧或本地 AI,有三条工程范式值得直接借鉴,而且都不依赖它的具体实现:一是遥测原生,把能耗、tokens-per-joule 做进核心数据模型而非事后埋点,且遥测失败绝不阻塞主流程;二是硬件感知的默认值,启动时探测 GPU/CPU/RAM 自动写出最优后端配置;三是复杂度路由即省电策略,用一个几乎零成本的分类器换取大幅能耗节省。它和 Hermes 的关系也不是非此即彼——一个是社区驱动、自我进化的实用主力,一个是可度量、可复现的研究平台,而且通过 agentskills.io 标准还能互相导入技能。对想理解本地优先 AI Agent 到底该怎么搭的人来说,这份源码本身就是很好的教材。项目地址与参考GitHub:github.com/open-jarvis/OpenJarvis(Apache 2.0)文档:open-jarvis.github.io/OpenJarvis项目主页:scalingintelligence.stanford.edu/blogs/openjarvis论文:arXiv 2605.17172Intelligence Per Watt:intelligence-per-watt.ai对比项 Hermes Agent:github.com/NousResearch/hermes-agent本文基于公开仓库源码阅读整理,代码片段为说明性节选,以仓库最新版本为准。
斯坦福 OpenJarvis 源码解读:一个“本地优先“AI Agent 框架是怎么设计的
最近开源圈又冒出一个 Agent 框架——OpenJarvis,出自斯坦福 Scaling Intelligence Lab 和 Hazy Research(就是 Christopher Ré 那个组),2026 年 3 月 12 日以 Apache 2.0 协议放出,作者列表里还挂着 Azalia Mirhoseini。和满天飞的 Agent 框架不同,它的口号是Personal AI, On Personal Devices,目标很直白:让个人 AI Agent 默认跑在你自己的设备上,只在真正必要时才调用云端。支撑这个口号的是它们之前的 Intelligence Per Watt 研究——本地语言模型已经能处理约 88.7% 的单轮对话/推理查询,且 2023 到 2025 年每瓦智能提升了 5.3 倍。一句话:模型和硬件都准备好了,缺的是把本地优先落地的软件栈。OpenJarvis 想做的,用它自己的话说,是本地 AI 时代的 PyTorch。这篇文章咱不复述官方介绍,而是把仓库 clone 下来,从源码角度拆它的几个核心设计:可插拔注册表、trace 驱动的本地/云路由闭环、把能耗写进数据模型的遥测层、四种 Agent 循环原型,以及它的 skill 机制与 Hermes 的本质区别。仓库整体是 Python(82.7%) Rust(8.7%) TypeScript(7.3%),核心逻辑在src/openjarvis/。一、五原语 一个注册表:可插拔的全部秘密先纠正一个流传的说法。外面文章爱说五原语,README 又说三个核心思想,其实对着代码看,真正的骨架是五个可插拔抽象 八条设计原则。而且它自己文档里都没完全统一:架构文档一处写Intelligence / Engine / Agentic Logic / Memory / Learning,另一处写Intelligence / Engine / Agents / Tools / Learning。对照core/registry.py的实际注册表,真相是 Memory 并非独立原语,而是挂在 Tools 之下的一种后端。所以规范的五原语是:Intelligence:模型目录,记录模型能力、参数量、定价(ModelRegistry)Engine:推理后端,抽象基类InferenceEngine,实现有 Ollama / vLLM / SGLang / llama.cpp / CloudAgent:推理循环,抽象基类BaseAgentTools(含 Memory):工具与记忆后端,BaseTool/MemoryBackend(SQLite / FAISS / ColBERT / BM25 / Hybrid)Learning:路由策略,抽象基类RouterPolicy(Heuristic / Learned / GRPO),横切其余四者让一切可插拔成立的,是一个泛型注册表加装饰器。核心就一个类RegistryBase[T],精髓在按子类名隔离存储:classRegistryBase(Generic[T]):classmethoddef_entries(cls)-Dict[str,T]:attr_namef_registry_entries_{cls.__name__}# 关键:按子类名隔离storagegetattr(cls,attr_name,None)ifstorageisNone:storage{}setattr(cls,attr_name,storage)returnstorageclassmethoddefregister(cls,key:str):defdecorator(entry):ifkeyincls._entries():raiseValueError(...)# 重复注册直接报错cls._entries()[key]entryreturnentryreturndecoratorf_registry_entries_{cls.__name__}这一行把每个子类的存储隔离开,于是EngineRegistry、AgentRegistry、RouterPolicyRegistry各自一套字典,注册时绝不串台。加一个新引擎只需要:EngineRegistry.register(my-engine)classMyEngine(InferenceEngine):defgenerate(self,messages,*,model,**kwargs):...单文件改动,没有中央工厂、没有 YAML、不用改任何已有代码,import 即注册。整个仓库有十几个这样的注册表(连后面提到的挖矿模块都有一个MinerRegistry),全部共享这一套机制。这就是设计原则里Pluggable Everything在实现层面的全部内容。二、Learning 闭环:本地还是云,是学出来的这是 OpenJarvis 区别于 LangChain / CrewAI 的真正内核——别家默认云推理,它把用哪个模型做成一个可学习的策略。拆成四个零件。① 复杂度评分器(learning/routing/complexity.py):纯正则,零模型开销,给 query 打 0~1 分。score0.0score0.20*length_score# 长度信号占 20%ifhas_code:domain_score0.7# 代码/数学信号占 25%# reasoning / multi-step / 问号数 / 子任务数 ... 加权汇总分数再映射到 token 预算档位(trivial1024 一路到 very_complex16384),思考型模型(qwen3.5 / r1 / o1 等)预算翻倍留 CoT 空间。整个过程不调用任何 LLM,所以路由决策本身几乎不耗算力。② 启发式路由器(learning/routing/router.py):六条规则按序短路。defselect_model(self,context):ifcontext.urgency0.8:# 规则5:急 → 最小模型(覆盖一切)return_smallest_model(available)ifcontext.has_code:# 规则1:代码 → 找名字带 code/coder 的return_find_model_by_tag(available,code)or_largest_model(...)ifcontext.has_math:# 规则2:数学 → 最大模型return_largest_model(available)ifcontext.complexity_score0.20:# 规则3:简单 → 小模型(省电)return_smallest_model(available)ifcontext.complexity_score0.55orcontext.has_reasoning:# 规则4:难 → 大模型return_largest_model(available)returnself._default# 规则6:兜底注意规则 5 把紧急度凌驾于一切之上——宁可用小模型快速本地出结果,也不甩给云。这正是本地优先在代码里的体现。③ 学习型路由器(learning/routing/learned_router.py):真正自我改进的部分。它不靠手写规则,而是从历史 trace 里学query_class → 最佳模型的映射,并且带一个置信门槛——样本不够就退回兜底。defselect_model(self,context):query_classclassify_query(context.query)if(query_classinself._policy_mapandself._confidence.get(query_class,0)self.min_samples):# 门槛默认 5 条样本returnself._policy_map[query_class]returnself._default策略表怎么更新?把所有 trace 按 query_class 分组,每个模型算一个复合分,取最高分写进策略表:defcomposite_score(self):srself.successes/self.count# 成功率fbself.feedback_sum/self.feedback_count# 用户反馈return0.6*sr0.4*fb# 6:4 加权④ 奖励函数(learning/routing/heuristic_reward.py):这里能耗/成本意识直接写进了公式。latency_scoremax(0,1-latency/max_latency)cost_scoremax(0,1-cost/max_cost)efficiency_scorecompletion_tokens/total_tokens reward0.4*latency_score0.3*cost_score0.3*efficiency_score延迟 0.4、成本 0.3、token 效率 0.3——把快、省钱、不啰嗦量化成了可优化的标量。四个零件接起来就是一个闭环:每次推理 → 记一条 trace → 分析成败与反馈 → 更新策略表 → 下一条同类 query 走更优模型。这就是它把本地优先从口号变成可优化系统的方式。三、Intelligence-per-Watt:能耗被写进了数据模型这是 OpenJarvis 最不一样的地方。别的框架顶多记延迟,它把能耗当一等公民。每次推理自动落库一条TelemetryRecord,字段里直接有焦耳和瓦特:dataclass(slotsTrue)classTelemetryRecord:latency_seconds:floatttft:float# 首 token 延迟cost_usd:floatenergy_joules:float# ← 焦耳power_watts:float# ← 瓦特...更狠的是它针对不同硬件写了独立的能耗采集器:energy_rapl.py(Intel CPU)、energy_nvidia.py、energy_amd.py、energy_apple.py。NVIDIA 那个不是估算,是直接读硬件计数器——Volta 以上的卡用nvmlDeviceGetTotalEnergyConsumption()读起止毫焦,差值除以 1000 就是焦耳;老卡才退回功率轮询做梯形积分。聚合器(telemetry/aggregator.py)算出来的指标才是论文标题的真身:avg_tokens_per_joule(每焦耳产出多少 token)、avg_energy_per_output_token_joules、avg_throughput_per_watt,甚至把 prefill 和 decode 两个阶段的能耗分开统计。换句话说,哪个模型在你这台机器上每瓦最划算是它能直接 SQL 查出来的数字。这也是它研究平台属性的核心——一切都要能 benchmark。顺带一提,读代码时还翻到一个意外模块mining/和MinerRegistry,它在对接一条叫 Pearl 的 PoUW(Proof-of-Useful-Work)区块链,让你本地跑推理的同时挖矿。验证端是纯 Rust plonky2 的 STARK 证明,硬件中立,只认数学对不对。这块还在早期,算是个挺斯坦福的彩蛋,这里不展开。四、八个 Agent,其实是四种循环原型agents/是仓库最大的模块(约 2 万行),但八个内置 Agent 拆开看本质只有几种推理循环,共享同一个BaseAgent抽象:Agent循环范式默认最大轮数工具调用方式simple单轮1无native_reactThought→Action→Observation10正则解析自由文本native_openhandsCodeAct(写跑 Python)3多格式兼容orchestrator多轮自动选工具—原生 OpenAI tool-callingoperative/monitor_operative常驻/持续20原生 tool-callingclaude_code/opencode/openhands子 Agent 委派—spawn 外部 agent这里有个贯穿全局的工程取舍:ReAct 和 CodeAct 用正则解析自由文本(因为本地小模型常常不支持原生 tool-calling),而 orchestrator / operative 走 OpenAI 原生 tool-calling(模型够强时)。这种双轨设计正是它能在弱本地模型上也跑起来的原因。ReAct 的run()就是一个朴素 for 循环:生成 → 解析 → 判断终止 → 执行工具 → 把 Observation 塞回上下文。for_turninrange(self._max_turns):ifself._loop_guard:messagesself._loop_guard.compress_context(messages)# 防上下文爆炸resultself._generate(messages)parsedself._parse_response(result[content])ifparsed[final_answer]:# 出现 Final Answer → 返回returnAgentResult(contentparsed[final_answer],...)ifnotparsed[action]:# 没有 Action → 当成最终回答returnAgentResult(contentcontent,...)tool_callToolCall(nameparsed[action],argumentsparsed[action_input])tool_resultself._executor.execute(tool_call)observationfObservation:{tool_result.content}messages.append(Message(roleRole.USER,contentobservation))# 喂回模型CodeAct(native_openhands.py)真正值得抄的不是会跑 Python,而是它的_extract_tool_call()同时兼容三种模型输出习惯——因为不同本地模型吐 tool-call 的格式五花八门:# 格式1:Action / Action Input(标准)action_matchre.search(rAction:\s*(.),text,re.IGNORECASE)# 格式2:tool_callname $keyvalue/tool_call(XML 风格)# 还分别兼容 $keyvalue、keyvalue/key、甚至 GLM 模型爱用的 key: value# 代码块直接执行matchre.search(rpython\n(.*?),text,re.DOTALL)这是本地优先被迫长出来的肌肉:云端 API 格式统一,本地一堆开源模型各说各话,框架只能在解析层把脏活全包了。常驻模式operative的核心在于跨会话状态持久化,状态和会话历史都落到 memory backend,用operator:{id}:state这种 key,还带一个兜底——即便模型自己忘了存状态,框架也会自动把回复摘要存下来:def_auto_persist_state(self,content):# agent 没显式存就自动存摘要summarycontent[:1000]self._memory_backend.store(foperator:{self._operator_id}:state,summary)最后是一个容易被忽略但很实用的防呆层loop_guard.py,用两招防 agent 卡死:对(工具名, 参数)做 SHA-256 哈希计数,同一调用重复超过 3 次直接拦截;再用滑动窗口检测 A-B-A-B 这种来回横跳。对要长时间无人值守跑的端侧 agent,这层几乎是必需品。max_identical_calls3# 同一 (tool, args) 重复超 3 次 → 拦ping_pong_window6# 检测 A-B-A-B 来回横跳max_context_messages100# 上下文超 100 条 → 压缩五、Skill 机制:和 Hermes 的本质区别Hermes(Nous Research 出品)是目前社区最火的 Agent,两者经常被拿来比。最有意思的是,我从代码里挖到一个关键差异——它俩号称的skill 自我进化根本不是一回事。先看 OpenJarvis 的 skill 数据模型。它不只是一段文档,而是一条可执行流水线(skills/types.py):dataclass(slotsTrue)classSkillStep:tool_name:str# 调用某个工具skill_name:str# 或者调用另一个 skill(可组合)arguments_template:str{}# Jinja 风格模板output_key:str# 结果存到上下文哪个 keydataclass(slotsTrue)classSkillManifest:name:strsignature:str# Base64 Ed25519 签名(可验真伪)markdown_content:str# 从 SKILL.md 加载的正文两个细节:skill 可以用skill_name调用另一个 skill(组合性);每个 skill 带 Ed25519 签名,因为要从外部仓库导入,得能验证没被篡改。README 那句每个 skill 都是一个 tool在代码里就是SkillTool适配器,tool_id fskill_{name},还能从模板里的{placeholder}自动反推出参数表。于是 agent 在工具目录里看到的 skill 和普通工具长得一模一样,按需调用。skills/sources/下有四个解析器,其中HermesResolver直接 git clone NousResearch/hermes-agent,遍历它的skills/category/skill/SKILL.md目录。能互导的前提是双方都遵循 agentskills.io 标准——所以jarvis skill install hermes:arxiv就能把 Hermes 的技能拿过来,反过来也行。而最关键的区别在自我进化的实现哲学,两者几乎相反:HermesOpenJarvis触发方式完成复杂任务(5 工具调用)后自主写新 skill攒够 trace(默认 ≥20 条)后批量优化已有 skill进化对象生成全新技能文档优化已有技能的描述 few-shot 示例进化时机在线、运行中离线、批处理进化引擎LLM 自己写DSPy / GEPA 优化器是否改原文件直接写/改不改原文件,写 sidecar overlay是否度量效果主要靠 agent 判断benchmark 量化 impact看learning/agents/skill_optimizer.py就明白了:它把 trace 按 skill 名分桶,够 20 条就跑 DSPy 或 GEPA,把优化结果写成一个叠加文件,而不动原版。classSkillOptimizer:def__init__(self,*,min_traces_per_skill20,optimizerdspy):# 或 gepa...defoptimize(self,trace_store,...):bucketsself._bucket_traces_by_skill(traces)# 按 skill 分桶forskill_name,skill_tracesinbuckets.items():iflen(skill_traces)self._min_traces:continue# 样本不够就跳过outputself._run_dspy(...)# 或 _run_gepa(...)# 结果写到 ~/.openjarvis/learning/skills/name/optimized.toml一句话总结这个区别:Hermes 是写新技能的生成式进化,OpenJarvis 是调优老技能的优化式进化。前者更野蛮生长、更像一个有自主性的个体;后者更克制、可度量、可回滚(overlay 不动原文件),骨子里是斯坦福那套一切都要能 benchmark的研究范式。写在最后把这几块拼起来,OpenJarvis 的设计意图就很清楚了:它不是又一个调用云 API 的 Agent 编排器,而是一套以本地执行为默认、把能效当成一等约束、用 trace 反馈持续优化路由与技能的研究型基础设施。如果你做端侧或本地 AI,有三条工程范式值得直接借鉴,而且都不依赖它的具体实现:一是遥测原生,把能耗、tokens-per-joule 做进核心数据模型而非事后埋点,且遥测失败绝不阻塞主流程;二是硬件感知的默认值,启动时探测 GPU/CPU/RAM 自动写出最优后端配置;三是复杂度路由即省电策略,用一个几乎零成本的分类器换取大幅能耗节省。它和 Hermes 的关系也不是非此即彼——一个是社区驱动、自我进化的实用主力,一个是可度量、可复现的研究平台,而且通过 agentskills.io 标准还能互相导入技能。对想理解本地优先 AI Agent 到底该怎么搭的人来说,这份源码本身就是很好的教材。项目地址与参考GitHub:github.com/open-jarvis/OpenJarvis(Apache 2.0)文档:open-jarvis.github.io/OpenJarvis项目主页:scalingintelligence.stanford.edu/blogs/openjarvis论文:arXiv 2605.17172Intelligence Per Watt:intelligence-per-watt.ai对比项 Hermes Agent:github.com/NousResearch/hermes-agent本文基于公开仓库源码阅读整理,代码片段为说明性节选,以仓库最新版本为准。