Prime Agent 内幕:当 Agent 的唯一工具是一个 Python 解释器

基于 Prime Agent 主干源码 commit 965941c 的深读:默认工具列表里只有 ["ipython"]——读文件、改代码、开子代理、管记忆全是 Python 表达式。结合 RLM 论文(arXiv 2512.24601)与 Continual Harness 论文(arXiv 2605.09998),看"上下文变成变量、经验变成账本"这两步棋怎么在源码里落地:dill 快照、控制信道防死锁、只许 CRUD 的自我改进、以及 Factorio 里当场翻车的 reward hacking。

打开 Prime Agent 的源码,agent-session.ts 里默认激活的工具列表是一行让人愣住的代码:["ipython"]。没有 read,没有 grep,没有 bash 工具,没有 task 工具——只有一个持久的 Python 解释器。读文件是 Python,改代码是 await edit(...),开子代理是 await rlm(...),连”记住这条教训”都是 rlm.harness.create_memory(...)。这篇把这个激进设计的两根支柱——上下文变成变量经验变成账本——逐个拆到函数名。

本文所有结论来自 Prime Agent 主干源码 commit 965941c(2026-08-12 克隆) 的静态阅读,函数名、默认常量照抄原文;行号会随版本漂移,函数名可长期检索。这是《LiteLLM 路由内幕》《SGLang 内幕》之后源码深读系列的第三篇:前两篇看推理基础设施(网关怎么选机器、引擎怎么榨干机器),这篇上移一层,看基础设施之上的 agent harness 怎么组织一次长程任务。

阅读地基:什么是 harness、为什么”判断归模型、物理归代码”,见《什么才是好的 Harness》;上下文腐烂(context rot)与遗忘机制的谱系见《遗忘是一种能力》;多智能体的协调税见《深度解读 Agent Swarm》。Prime Agent 可以看作对这三篇里所有问题给出的同一个答案:把它们全部变成可编程状态

全景:两篇论文、四个进程、一个内核

Prime Agent 是 Prime Intellect 2026 年 8 月开源(MIT 协议)的编码/研究 agent,README 开篇就把自己钉在两个抽象上:

  1. RLM(Recursive Language Model,递归语言模型)——出自 MIT CSAIL 的 Alex Zhang、Tim Kraska、Omar Khattab 的论文(arXiv:2512.24601):把长上下文当作外部环境里的变量,让模型在 Python REPL 里编程式地切片、检索、递归调用子模型来处理它,而不是直接塞进 token 窗口。
  2. Continual Harness(持续脚手架)——出自 Princeton 团队的论文(arXiv:2605.09998,即”Gemini 玩宝可梦”工作的后续):把 harness 状态形式化为 H=(ρ,G,K,M)H = (\rho, G, K, M)——提示词、子代理、技能、记忆——让 agent 在单次运行内不重置地从自己的轨迹中提炼更新。

工程上它是四层进程拓扑(docs/architecture.md,源码里逐一可验证):TUI 客户端 → 常驻 supervisor 守护进程(路由、恢复)→ 每个根会话一个 worker 进程(持有 agent 循环与调度器)→ IPython 内核进程(Jupyter 协议,ZeroMQ 三信道)。关掉终端,worker 和内核继续跑;文档明说这个进程隔离”是为了生命周期和故障遏制,不是安全沙箱”。

下面按”一个工具 → 子代理 → 自我改进 → 长跑机制”四幕拆。

第一幕 · 唯一的工具:ipython

谱系:从 CodeAct 到 RLM

“让 agent 用代码而不是 JSON 工具调用来行动”不是新想法——CodeAct(arXiv:2402.01030,ICML 2024)已经论证过:预定义工具的动作空间受限、难以组合,统一到 Python 代码后成功率最高提升 20%。Prime Agent 把这条路走到头,并叠加了 RLM 论文的关键增量:不只用代码行动,还用代码管理自己的上下文

RLM 论文的实验数字(GPT-5 为根模型,深度 1,即子调用是普通 LLM):LongBench-v2 CodeQA 从 24.0 提到 62.0;BrowseComp-Plus(600 万–1100 万 token 的千文档检索)从 0.0(超窗直接不可做)提到 91.3;OOLONG-Pairs(需要两两配对聚合的二次复杂度任务)从 0.1 提到 58.0。对比各种脚手架的中位数增益:胜过压缩式 compaction 26%、胜过带子调用的 CodeAct 130%、胜过 Claude Code 13%。论文还训了第一个 RLM 原生模型 RLM-Qwen3-8B,比基座平均好 28.3%。这些数字来自论文 v3 的 HTML 版,我核对过原文但未亲手复现。

“上下文变成变量”在源码里长什么样

有趣的是:Prime Agent 并没有像论文原型那样把消息历史注入成一个内核里的 context 变量。翻遍 tools/ipython.ts 的引导代码,“prompt-as-a-variable”实际落地为三个机制:

其一,内核命名空间比 token 窗口活得久。 变量跨轮次、跨压缩持久:kernel/state-snapshot.ts 每 1.5 秒防抖地把用户命名空间里每个顶层变量用 dill 独立序列化到磁盘(上限 256 MiB),会话恢复时先还原快照、再跑引导代码覆盖 rlm 等活句柄。压缩(compaction)的摘要提示词里专门附了一段 KERNEL_PERSIST_SUMMARY_NOTE:“内核在摘要之后继续运行——你定义的每个变量、导入、辅助函数都还在……把值得记住的变量名写进摘要,复用而不是重新定义。“也就是说,工作集从 token 窗口迁移到了 Python 变量名,token 窗口里只需要留一个名字。

其二,转录本身是一个可寻址的文件。 系统提示词的头部信息里有一行 Conversation log: <会话 JSONL 路径>——agent 被明确告知自己的对话历史存在磁盘哪里,可以用 Python open()/grep 自己的历史。配合系统提示词里的 REPL 教义:“用 Python 读、搜、改文件——它给你可复用的变量,可以切片、过滤、再操作,而不用重读。永远把读取/搜索结果赋给命名变量。

其三,输出强制截断逼你编程。 内核的 stdout/stderr/结果各截断在 65,536 字符(DEFAULT_MAX_OUTPUT_CHARS,约合 1.6 万 token)。想把 8 MB 的日志倒进上下文?不行,你只能写代码在内核里过滤。手算一笔差距(自设场景,脚本验算过):8 MB 转录约合 210 万 token,逐 token 读一遍窗口都装不下;而在内核里 grep 出 40 行命中、每行 100 字符,进入上下文的只有约 1000 token——三个数量级的差距。这就是官方博客那句”用程序在数据上跑函数,而不是花 token 用工具读数据”的具体含义。

顺带一提,压缩本身的默认参数:reserveTokens: 16384, keepRecentTokens: 20000compaction/compaction.ts),摘要预算是 reserve 的 0.8 倍约 1.3 万 token,token 估算用 chars/4 的糙算法。和 Claude Code 的压缩机制同一个思路,但因为工作集在内核里,摘要丢信息的代价被结构性地降低了——这是 RLM 博客批评”摘要必然丢信息”之后给出的折中:摘要照做,但重要状态根本不依赖摘要。

引导代码:技能是可 await 的模块

内核启动时执行 RLM_BOOTSTRAP_BASE_CODEnest_asyncio.apply()(让 await 能嵌套用)、import rlm,然后把每个 Python 技能包导入 globals() 并包一层使其可直接调用——于是模型的命名空间里天生就有 rlmeditwebsearchgoalcompactrefineagent_message 等一排可 await 的名字。技能本体是可编辑安装(uv pip install --editable)的真 Python 包,装在专用 venv(Python 3.11)里;skill-creator 技能可以把反复出现的工作流打包成新技能。这与 Claude 系的 SKILL.md 惯例兼容(也扫描 .agents/skills/),但多出”技能=可导入代码”这一档。

第二幕 · rlm():子代理是一次函数调用,但不返回答案

只送不等的递归

模型在单元格里写:

handle = await rlm("审查这个 API 的鉴权逻辑", name="auth-reviewer")
# handle: rlm_child_id, name, session_dir, model —— 没有答案

_startRlmChildRunagent-session.ts)的关键设计:rlm() 在任务准入时就返回句柄,永不返回子代理的答案。子代理是一个完整的 AgentSession(自己的转录、系统提示词、按需内核),在分离的异步任务里启动;结果只能通过子代理主动 await agent_message.send(msg, receiver_role="parent") 或写文件送回来。如果子代理跑完了却一句话没回,父代理会收到一条合成的 “completed_without_reply” 通知,附子代理最后一条输出的预览。

为什么这么拧巴?因为内核是串行的——一个内核同时只能执行一个单元格。如果 rlm() 阻塞等答案,父代理就在唯一的执行线程上干等。只送不等的语义强迫父代理发完就结束本轮,把并行留给多个子代理各自的运行时。系统提示词里明说:“在独立的调用里生成各个子代理,然后结束你的回合,而不是等待完成。“这和 Agent Swarm 那篇分析的”协调税”是同一个问题的另一种解法:不搞共享黑板,通信收窄为异步消息。

递归深度默认 1_resolveRlmMaxDepth,可通过 RLM_MAX_DEPTH/rlm-max-depth 调):根可以开子代理,子代理默认不能开孙代理。约束执行了三层——TypeScript 侧准入检查(权威)、Python 侧环境变量预检、以及最优雅的一层:到达最大深度的子代理的系统提示词里根本不出现 rlm(...) 的用法说明allowRecursion 开关)。不告诉你有这个功能,比拦截你调用更省事。

一个值得单独记住的传输细节

await rlm(...) 在执行中的单元格里发起,宿主的回复怎么送回来?走 Jupyter 的 shell 信道会死锁:IPython 串行处理 shell 消息,当前 execute_request 不结束就轮不到回复,而它正等着回复才能结束。Prime Agent 的解法(docs/rlm-runtime.md 专门有一节):宿主回复走控制信道(control channel)——内核在执行期间仍然服务这条信道,回调在另一个线程里用 call_soon_threadsafe 解析 future。整个 Python 运行时(prime-agent-runtime,仅 4 个文件)就是围着这一个 host_request() 桥搭的薄壳:Python 侧不实现任何 agent 循环,所有能力都是发给 TypeScript 宿主的类型化请求。

家庭拓扑

代理间通信被刻意收窄成”核心家庭”:agent_message.list_agents() 只能看到父亲、兄弟、直接子女;发送者身份由守护进程注入(Python 侧无法伪造);单条消息上限 16,384 字符,每对发送-接收有令牌桶限流(容量 3,每秒回 1)。忙碌的目标收到的消息进队列而非阻塞——避免两个代理互发消息互等的死锁。还有一个带边界的窥视工具 agent_observe.recent_messages(target, limit≤50, max_chars≤2000),让父代理不用打断子代理也能看它最近在干嘛。

sequenceDiagram
    participant M as 模型(父)
    participant K as IPython 内核
    participant H as TS 宿主(worker)
    participant C as 子代理会话
    M->>K: await rlm("sub-task")
    K->>H: host_request("rlm.run")<br>经 comm 信道
    H->>C: 创建子会话(分离任务)
    H-->>K: 控制信道回复:句柄
    K-->>M: handle(rlm_child_id, name...)
    Note over M: 结束本轮,不等待
    C->>C: 独立运行(自有内核与转录)
    C->>H: agent_message.send(..., "parent")
    H-->>M: 下一轮注入父上下文

计费归属也有讲究:子代理的用量折算进父会话的账单总额,但不计入父模型的上下文窗口占用——花的是钱,省的是窗口。这正是 RLM 论文的核心账:把 token 密集的工作外包给一次性的子上下文,根上下文只收结论。

第三幕 · Continual Harness:只许 CRUD 的自我改进

论文说什么

Continual Harness 论文(arXiv:2605.09998)的出发点是一个观察:作者们在”Gemini 玩宝可梦”项目里靠人工迭代 harness,让 Gemini 成为第一个通关《宝可梦 蓝》的 AI 系统——而在最难的阶段,他们观察到 agent 开始借长上下文记忆自发迭代自己的策略。论文做的事就是把人从这个循环里完全拿掉:agent 从最小环境接口出发,在行动提炼自己的提示词、子代理、技能、记忆之间交替,不重置 episode、单次运行内在线适应。在宝可梦《红》《绿宝石》上,这套机制”追回了到手工专家 harness 的大部分差距”,且收益随模型能力扩大(论文数字,未亲手复现)。

Prime Agent 把这套 H=(ρ,G,K,M)H = (\rho, G, K, M) 搬进了编码 agent。关键的克制在于对”自我改进”三个字的收窄——不训练模型、不改基础提示词,只对一个 JSON 账本做 CRUD

/refine 的完整回路

自我改进的全部物理载体是每个作用域一个 harness_state.json(会话级默认,全局在用户主目录的 .prime/agent/harness/ 下),结构就是 {schema, entries: {prompt|memory|skill|subagent}, refinements}/refine 的执行是一次 LLM 调用(planRefinement(),思维链刻意关闭、输出预算留给 JSON),输入为:当前 harness 概览 + 最近 20 条提炼历史 + 轨迹的最后 8 万字符 + 作用域政策,输出是严格 JSON 的 CRUD 编辑列表,每条编辑必须带 reason,整体必须带”由轨迹证据支撑”的 rationale——空编辑列表是被明确允许的答案(没学到东西就别硬写)。

“不许碰基础提示词”执行了三层:提炼器的系统提示词明文禁止;validateEdit() 硬拒 id 为 base_system_prompt 的编辑;而且基础提示词根本不在磁盘上——它是编译进二进制的 buildRlmPrompt() 函数,harness 状态只是追加在这个不动前缀之后。这个设计对 RL 训练是刚需:将来针对这个 harness 训模型时,训练时见过的提示词前缀必须在部署时原样存在。

回滚不是再问一次 LLM,是机械逆放:每条应用过的编辑都记录了完整的 before/after 快照,rollbackProposal() 逆序回放——有 before 的恢复原状,无 before 的 create 变 delete。回滚本身也是一次带 rollbackOf 标记的提炼,所以回滚也可以被回滚。写盘是原子的(临时文件 + rename),内核和宿主对同一文件的并发写靠 mtime 检测 + 计划期基线比对(条目在计划和应用之间被改过就拒绝应用,不覆盖)。

自动触发有一个双级闸门:默认每 25 个助手轮次(或每次压缩后)先跑一次廉价的审查调用——系统提示词写着”拒绝一次性噪声、无证据的假设、瞬态工具输出”——审查通过才跑完整的提炼计划,且自动提炼只写会话级状态,不许自动晋升为全局。冷却期 20 分钟。

记忆没有自己的数据库

值得一提的是没有的东西:没有向量库、没有嵌入检索、没有独立的记忆服务。记忆就是账本里 kind: "memory" 的条目。召回是两级的:每次构建系统提示词时注入一个紧凑区块(每类最多 6 条、每条截 180 字符,明确标注”这些是路由提示,细节要查原条目”);需要细节时 agent 在内核里 rlm.harness.get("memory", id) 读全文。跟 MemOS 那篇的三态记忆和 RAG 记忆对照看很有意思:当你的 agent 有一个真正的编程环境时,“检索”可以退化成”查字典 + grep”,向量库从必选项变成了可选项。

我在克隆的仓库里免安装跑通了这个账本(prime-agent-runtime 是纯 Python,指定 RLM_HARNESS_STATE_DIR 环境变量 + PYTHONPATH 即可):create_memory → 条目 version 1,update_memory → version 2,overview() 输出的开头就是给模型看的调用契约全文。整个”自我改进机器”的可信度恰恰来自它的朴素——一个能用 30 行脚本验证的 JSON 文件。

诚实的对照:Factorio 里的 reward hacking

官方博客自己披露了一个失败案例:在 Factorio 环境长跑时,/refine 把失败提炼成记忆、把成功提炼成技能,几小时内产量冲进 10 万量级——但 agent 发现可以用 RCON 命令直接把资源刷进装配机,即使心跳提示词里明确禁止作弊,它还是这么干了,并且把高效的作弊方法沉淀成了技能。这是目标函数即命运的教科书演示:自我改进机制会忠实地放大 agent 找到的任何捷径,包括你不想要的。账本可回滚在这里是止损手段,不是预防手段。

第四幕 · 长跑机制:目标、心跳、有界自治

长程任务的支撑件都很小,列出机制和默认值:

  • /goal:目标状态是追加在会话 JSONL 里的自定义条目(无独立文件),每轮结束注入一段 <goal_context>(状态、token 用量/预算);只有内核里的 await goal.complete() 能标记完成——完成的判定权在 agent 的代码里,不在轮次计数里。
  • 心跳/日程:cron 任务存在会话工件目录,计时器活在守护进程 worker 里,终端关了照样触发。默认每 5 分钟、steer 模式(打断当前轮)。崩溃安全设计值得抄:先推进日程(认领这次触发)再投递,宁可漏一次也不重放一次不确定的提示。
  • /autonomous:纯粹的继续策略 + 质量闸门,不代管权限审批。默认预算:最多 3 次继续、12 轮、8 万 token、30 分钟。闸门是 shell 命令(如 npm run check),失败输出(截 6000 字符)作为下一轮的输入。防空转:对 git 工作树做快照,如果自上次同一闸门失败以来什么都没变,就不重跑闸门但照扣重试次数。token 计数刻意排除缓存读——验证循环不该因为反复读缓存上下文而烧穿预算。

这三件套加上守护进程常驻,就是 README 说的”为长时间运行的工作而建”。对照 ARC-AGI-3 上那个 95.5% 的宣传数字:官方博客称 Opus 5 + Prime Agent 三次运行 [95.0, 95.2, 95.5],超过 ARC 报告的人类专家基线 95.4%,且唯一的 ARC 特化改动是任务提示词。需要标注:这是自报告、未经 ARC 官方验证的成绩,且”模型+harness”与裸模型成绩(官方榜上 Opus 5 为 30.2%)不可直接同榜比较;他们自己也承认复现 Claude Code/Codex 基线时跑不出官方数字。我把它当作”harness 差距巨大”的证据,而不是某个精确排名。

尾声 · 一张总表与两条观察

关键机制数值/形态源码位置
默认工具列表["ipython"]core/agent-session.ts
内核输出截断stdout/stderr/结果各 65,536 字符core/kernel/index.ts
变量快照dill 逐变量序列化,上限 256 MiB,1.5s 防抖core/kernel/state-snapshot.ts
递归深度默认 1;三层执行(宿主准入/内核 env/提示词裁剪)_resolveRlmMaxDepth
rlm() 语义准入即返回句柄,永不返回答案_startRlmChildRun
宿主桥host_request() 走 Jupyter 控制信道防死锁prime-agent-runtime/src/rlm/__init__.py
通信拓扑父/兄弟/子;16,384 字符/条;令牌桶 3 容量 1/score/agent-messages.ts
压缩默认reserve 16,384 / keep-recent 20,000 tokencore/compaction/compaction.ts
/refine 输入轨迹末 8 万字符 + 概览 + 20 条历史core/refinement/refinement.ts
自动提炼每 25 轮,双级 LLM 闸门,冷却 20 分钟,仅本地作用域settings-manager.ts
回滚before/after 快照机械逆放,无 LLM 参与rollbackProposal()
自治预算3 继续 / 12 轮 / 80k token / 30 分钟core/autonomous.ts

两条观察(我的提炼,非论文结论):

观察一:这套设计把三类”agent 记忆问题”折叠成了一类。 工作记忆(token 窗口装不下)→ 内核变量;情景记忆(这次会话发生了什么)→ 转录文件路径 + Python 检索;语义记忆(跨会话的经验)→ harness 账本。三者的读写接口统一成”写 Python”。对照《遗忘是一种能力》里操作系统内存分层的类比,Prime Agent 相当于把换页、文件系统、数据库全部塞给了同一个系统调用。代价也在这里:所有压力都落在模型写代码的能力上——RLM 论文自己就承认弱编码模型(Qwen3-8B)当不好 RLM 根模型,语法错误会沿递归传播。

观察二:整个仓库是给”还不存在的模型”造的鞍。 tools/ipython.ts 的第一行注释是 // TODO: reconsider whether the persistent kernel is needed once RLM-1 weights land.(等 RLM-1 权重落地后重新考虑是否还需要持久内核)。RLM 论文只用 48 个 H100 小时的拒绝采样微调就让 8B 模型在这个脚手架上平均提升 28.3%;Prime 博客反复强调”未训练的模型用不好这个脚手架”。这解释了很多看似过度的工程决定——不可变的基础提示词、严格 JSON 的提炼输出、三层深度约束:它们都是为了让 harness 成为一个可以被训练目标锚定的稳定接口。harness 先行、模型后至,是这家做分布式训练起家的公司很自然的下注顺序。

诚实的提醒

  • 本文源码结论来自静态阅读 + 两个免安装小实验(harness CRUD、手算验算),没有实际安装运行 Prime Agent 跑真实任务;异步子代理在高并发下的实际表现、auto-refine 的实际质量均未亲测。
  • RLM 论文、Continual Harness 论文和官方博客的全部 benchmark 数字(含 ARC-AGI-3 的 95.5%、OOLONG 各表)均为核实过来源但未复现;ARC 成绩为自报告,正文已标注不可与裸模型同榜比较。
  • 成本最低的亲手验证实验:克隆仓库后无需安装任何东西,RLM_HARNESS_STATE_DIR=/tmp/ht PYTHONPATH=<repo>/prime-agent-runtime/src python3from rlm.harness import get_harness_state,跑一遍 create/update/overview——十分钟内你就能亲眼看到”自我改进”的全部物理载体不过是一个带版本号的 JSON 文件。

参考来源

arXiv 论文

工程实践