Prime Agent 源码深度解析:会自我改进的 RLM 编程智能体是如何构建的

从 RLM 编程模型、跨语言 host 桥到 Continual Harness 的 Prime Agent 实现原理解剖

Posted by 爱折腾的工程师 on Tuesday, August 11, 2026

一、引言:一个"把智能体当编程范式"的项目

Prime Agent:会自我改进的 RLM 编程智能体

如果你用过 ChatGPT Code Interpreter 或 Claude Code,可能已经习惯了"模型在沙箱里跑 Python"这个设定。但绝大多数实现只是把 Python 当作执行工具:模型生成代码,环境执行,结果回传,仅此而已。

Prime Agent(开源,MIT License)走得更远——它把 Python 当作模型的持久工作环境,并提出两个核心抽象:

  • RLM(Recursive Language Model,递归语言模型):把上下文视为变量(prompt-as-a-variable),把工具调用视为函数调用(programmatic tool / sub-agent calling),整个会话是一个持久 REPL。模型可以用 await rlm("子任务") 像调用函数一样派生子代理。
  • Continual Harness(持续装备):把补充提示词、记忆、技能描述、可复用的子代理规格存成可持久化、可小步改进的状态/refine 复盘当前轨迹后,用有证据支撑的小更新沉淀经验——而基础 system prompt 永不重写,快照可回滚。

一句话概括它的设计目标:让有用的工作上下文和可复用的操作模式,活过单个聊天窗口。 长任务在终端断开后继续跑、子代理在后台并行、agent 之间可以直接通信、会话可以 detach 后重新 attach——这些能力在 README 里被反复强调,但真正有趣的是它们如何落到代码上。

项目概貌

指标 数据
语言 TypeScript(宿主/CLI/UI)+ Python(内核运行时)
包结构 npm workspaces monorepo:ai / agent / coding-agent / tui 四包
Python 运行时 prime-agent-runtime(独立 wheel,ipykernel 内核)
版本策略 全包锁步(lockstep),无 major 版本;patch 为修 bug 和新特性,minor 为破坏性 API 变更
工具链 tsgo / Biome / Vitest / esbuild(bundle)/ bun compile(二进制)
关键依赖 Jupyter 生态(ZeroMQ / ipykernel / comm)、typebox、zod-to-json-schema、zeromq、photon-node(图像处理)
基线 构建在 pi(Earendil Works 的 agent/TUI)之上

本文导读

  • 先建全局心智:第二节用一张总览图串起 TUI → AgentConnection → Daemon → Session Worker → Kernel 五层。
  • 再看双语言分工:第三节解释"为什么是 TypeScript + Python 两套栈"以及它们如何协作。
  • 再抓核心模块:第四、五节拆解 ai 统一 API 层、AgentSession 主循环、Python 内核桥、RLM 子代理、Harness/refine 与 TUI 差分渲染。
  • 最后看亮点与落地:第六节归纳关键设计决策,第七节给出部署运行说明。

二、整体架构概述:五层分工,各司其职

Prime Agent 的架构可以用一句话概括:客户端负责渲染,守护进程负责编排,Worker 负责执行,Kernel 负责"被模型驱动",持久化负责"活过会话"

Prime Agent 整体架构

看图重点:左侧从上到下是 Client → AgentConnection → Daemon Supervisor → Session Worker(含 Root AgentSession、Scheduler、IPython Kernel、RLM 子运行);右侧是 Model Providers 与持久化两列支撑。

逐层来看:

  1. Client 层packages/tui + modes/):交互式 TUI、print/json/rpc 等 headless 客户端。客户端不拥有执行权——它只渲染、接收键盘输入、维护本地 UI 偏好。
  2. AgentConnectionagent-connection/types.ts):客户端侧的执行边界契约。TUI 只依赖这个接口,不直接碰 AgentSessionRuntime。架构文档特别强调它"刻意保持为本地客户端契约",为将来接入远程适配器预留了边界。
  3. Daemon Supervisor:负责会话发现、命令路由、附件管理、worker 健康检查和跨 agent 消息投递。Session worker 与 kernel 是独立进程——这是生命周期与故障的隔离手段,但 README 明确警示:这不是安全沙箱,worker/kernel 与客户端同权限运行。
  4. Session Worker:一个 worker 拥有一个根会话树——AgentSessionRuntime(生命周期外壳)、AgentSession(全部执行逻辑)、Scheduler、根 IPython Kernel,以及所有 RLM 子运行。
  5. 持久化:Session 以 JSONL transcript + 工件存储;harness 状态与 refinements.jsonl 单独管理;session-lease.ts 用文件锁防止多进程并发写同一会话。

架构文档(docs/architecture.md)里的 prompt 执行链路值得注意:从会话队列开始,无论 prompt 来自用户、heartbeat、cron 调度、goal 续跑、autonomous 模式还是另一个 agent,走的是同一条执行与持久化路径。 这是"长任务持续续跑"能成立的基础——输入来源多样,但执行引擎只有一个。


三、技术栈选型:为什么是 TypeScript 宿主 + Python 内核?

这是整个项目最反直觉、也最值得思考的决策。绝大多数 AI 编程工具(Claude Code、Cursor、OpenAI Codex CLI)都是单一语言栈,而 Prime Agent 选择让模型直接生活在一个真实的 IPython 内核里

TS 宿主与 Python 内核协作

看图重点:左列是 TS 宿主职责(策略/凭据/子代理/持久化),右列是 Python 内核(模型可执行的薄封装),中间是 Jupyter comm 桥。

分工原则是:Python 侧只做"模型可执行的薄封装",所有重逻辑都在 TypeScript 宿主侧。

  • TypeScript 侧:凭据管理(env-api-keys.tsauth.json)、子代理执行(rlm-runtime.tsrunRlmChild())、上下文压缩(compaction/)、目标管理(goals.ts)、harness 状态(refinement/)、transcript 持久化(session-manager.ts)。这些涉及授权、并发、文件系统,TypeScript 有类型系统兜底,适合做"可靠的中枢"。
  • Python 侧prime-agent-runtime):rlm 模块让模型可以 await rlm(...)harness.py 提供 harness 状态 CRUD;mcp_base.py 把 MCP 服务器工具动态绑定成 async 方法;skill.pytyro 让技能 CLI 按类型签名自动生成参数解析。

pyproject.toml 的依赖只有三个,选型理由非常明确:

dependencies = ["ipykernel", "nest-asyncio", "tyro"]
  • ipykernel:内核基石。整个 host 桥基于 Jupyter 的 comm 通道实现,复用 Jupyter 生态(comm、display、kernel 生命周期)而不是自造 IPC。
  • nest-asyncio:IPython 内核本身跑在一个 asyncio loop 里,而 host_request 需要 asyncio.Future 跨线程等待 comm 回调,需要用 nest_asyncio 允许事件循环嵌套。
  • tyro:让技能 CLI 用类型签名自动生成参数解析,免去手写 argparse。

对模型来说,这套设计意味着什么?它可以在 IPython 里直接写:

import rlm
handle = await rlm("帮我并行调研三个 API 的定价", model="anthropic/claude-sonnet-4")

子代理是函数调用,返回值是可编程使用的句柄——这就是 RLM 编程模型的核心体验。


四、核心模块详解

4.1 packages/ai:统一 LLM API 层

如果"AgentSession 是执行引擎",packages/ai 就是"模型的统一插座"。它屏蔽了 31 个 provider 的差异(OpenAI、Anthropic、Google、Bedrock、Mistral、xAI、Groq、OpenRouter、Cloudflare 等)。

核心抽象定义在 src/types.ts

  • KnownApi 有 9 种 API 形态(openai-completionsanthropic-messagesopenai-responsesgoogle-generative-ai 等),KnownProvider 有 31 个 provider。
  • 标准化事件流:所有 provider 都产出 text / tool_call / thinking / usage / stop 五种粒度的流式事件(AssistantMessageEvent),每个增量事件都携带累计的部分结果 partial: AssistantMessage

一个值得称道的设计是请求失败编码进流,而非抛异常types.ts 注释明说这是契约,而底层的 EventStream<T, R>utils/event-stream.ts)是一个可 push 的 AsyncIterablepush() 逐个投递事件,[Symbol.asyncIterator]() 供消费方拉取,result() 返回最终结果的 Promise。消费方可以随时决定"流完了再要结果",而不是被同步异常打断。

模型发现也做得非常工程化:scripts/generate-models.ts 是一个离线构建脚本,运行时从 models.dev、OpenRouter 目录、Vercel AI Gateway、Prime Inference 实时目录抓取模型清单,合并手工元数据覆盖(thinking 级别映射、featured 旗舰标记),生成 533KB 的 models.generated.ts。构建流程里 npm run build 会先跑 generate-models 再编译——模型目录是构建产物,不是手写维护的。仓库规则还专门规定:永远不要直接改 models.generated.ts,要改就改生成脚本。

4.2 AgentSession:约 1.1 万行的主循环

packages/coding-agent/src/core/agent-session.ts 是整个系统的"心脏",单文件约 11200 行。它的主循环基于 session-action-store状态机队列:每个输入经历 queued → selected → preparing → committing → running → completed(或 failed/cancelled)。

关键点:

  • admission 入队_admitSessionInput() 把输入塞进队列,能立即执行的就标记 starts_when_admitted
  • pump 单飞循环_pumpSessionInputs()waitForIdle() 确保一次只跑一个 turn,然后成批启动预处理的 turn。
  • transcript 持久化:每条消息通过 sessionManager 追加为 JSONL entry,turn 的 delivery records 标注消息是否已 durable 落盘。
  • 自动重试_isRetryableError()(约 9971 行)区分"可重试错误"与"上下文溢出"——溢出不重试,交给 compaction 处理
// agent-session.ts
private _isRetryableError(message: AssistantMessage): boolean {
	if (message.stopReason !== "error" || !message.errorMessage) return false;
	// Context overflow is handled by compaction, not retry
	const contextWindow = this.model?.contextWindow ?? 0;
	if (isContextOverflow(message, contextWindow)) return false;
	...
	return true;
}

这个"重试 vs 压缩"的分流非常符合直觉:模型超载、限流、服务器 5xx 是暂时性的,重试合理;但上下文塞满不是运气问题,重试只会再浪费一次 token,压缩才是正解。

AgentSessionRuntimeagent-session-runtime.ts)则是生命周期外壳:newSession / switchSession / fork / importFromJsonl 都走同一套"teardown → replace → finish"模式,配合 session-lease.ts 的进程间租约防止多进程并发写同一会话文件。

4.3 Python 内核与 typed host request 桥

这是全项目最精妙的部分。TS 与 Python 之间的通信基于 Jupyter comm + ZeroMQcomm target 固定为 "host.request"

typed host request 时序

看图重点:Python 创建 asyncio.Future → 打开 comm → TS 侧 handleHostRequest 按 type 分派 → AgentSession 执行(如 runRlmChild)→ 结果经 comm_msg({status:"ok", ...}) 回传 → Future resolve,模型拿到句柄。

Python 侧实现(prime-agent-runtime/src/rlm/__init__.py:84-140)的 host_request() 有一行非常值得学习的安全细节:

comm.on_msg(_on_msg)
# request_type goes last so a payload "type" key cannot reroute the request.
comm.open(data={**(payload or {}), "type": request_type})

请求类型字段放在 payload 的最后,防止 payload 里自带 type 键覆盖请求类型、劫持路由。 这是"输入不可信"原则在协议层的体现——模型生成的代码可以携带任意 payload,但永远改不掉请求类型。

错误处理也很有讲究:按 reply 的 status 分派 ok / error / 意外三种情况,用 loop.call_soon_threadsafe 把结果安全写回 asyncio Future(因为 comm 回调可能来自其他线程)。

4.4 RLM 子代理:rlm(...) 是怎么变成真实子进程的

从模型视角,await rlm("子任务") 是一次函数调用;从实现视角,它是一条从 Python 到 TS 的 host request,触发 AgentSession.runRlmChild()agent-session.ts:9955),创建一个拥有独立 session runtime、可选独立 kernel 的子代理,任务 admit 后立即返回 RLMSpawnHandle(非阻塞)。

RLM 子代理流程

看图重点:主会话分派多个 RLM Child(可并行/后台),返回的 RLMSpawnHandle 让子代理"可编程地"被观察、attach 与收口。

几个有意思的细节:

  • RLMSpawnHandle 是 frozen dataclass:rlm_child_id / name / session_dir / model——子代理的"身份卡"。
  • 子代理创建后会写入会话树,rlm-max-depth/rlm-max-depth 命令)限制递归深度,防止子代理无限套娃烧钱。
  • daemon 断开后子代理继续运行,这是"长任务不中断"承诺的一部分;运行中的 agent 之间还可以直接互相发消息(agent-to-agent communication),不必都经用户中转。

4.5 Continual Harness:/refine 的自我改进闭环

Harness 的持久化状态在 Python 侧(harness.pyHarnessState),而校验与应用的策略逻辑在 TS 侧refinement/refinement.ts)——又一次体现了"Python 薄封装、TS 重逻辑"的分工。

Continual Harness 工作流

看图重点:轨迹 → 模型提议 edits(create/update/delete prompt/memory/skill/subagent)→ 逐条校验(结构合法 + baseline 比对)→ 应用(version +1,记录 refinements)→ 未来会话受益,形成闭环。

applyRefinementProposal()refinement.ts:707)里的并发防护值得一提。refine 是"先规划后应用"的两步操作,规划期间 harness 状态可能被其他进程改动,所以应用前会做 baseline 比对:

if (
	options.baselineState &&
	!proposalModifiedKeys.has(entryKey) &&
	JSON.stringify(before) !== JSON.stringify(baseline)
) {
	appliedEdits.push({
		...edit, id, before,
		applied: false,
		error: "entry changed during refinement planning",
	});
	continue;
}

发现"规划期间条目被改过"就拒绝应用这条 edit——乐观并发控制。应用成功的条目 version +1,整个 proposal 记录进 refinements.jsonl,harness 快照支持 rollback。每条 edit 还带 evidence(依据),保证改进不是模型拍脑袋。

另一个细节:harness.py_HarnessProxy 用延迟解析解决 forkserver 预导入时的时序问题——harness 状态在模块 import 时可能还没就绪,所以做成代理,用到才解析。

4.6 packages/tui:为流式输出而生的差分渲染

TUI 是整个项目中"性能打磨"最密集的部分。它的核心模型是:组件声明式渲染成纯文本行数组 render(width): string[],TUI 统一做逐行差分,输出最小 ANSI patch——组件不需要知道终端坐标。

性能优化贯穿三层:

  1. 渲染节流requestRender() + 16ms 最小渲染间隔(MIN_RENDER_INTERVAL_MS),高频的流式 token 事件只在每帧末合并重绘一次。
  2. 差分重绘:对比上一帧与当前帧的行,只重绘 firstChanged → lastChanged 区间;用 \x1b[?2026h/l(synchronized output)包裹整个重绘过程防闪烁。
  3. 保留视口:长会话流式更新时,如果改动发生在视口上方,走 preserveViewport 路径原地重绘可见窗口,不触碰终端 scrollback——否则每帧回放全屏会闪烁跳顶。

markdown.ts按块渲染缓存尤其聪明:

// Per-block render cache so streaming appends only re-render the changing
// final block instead of the whole document. Keyed by width/type/nextType/raw;
private blockCache = new Map<string, string[]>();

每个 Markdown 顶层块以 width|type|nextType|raw 为 key 缓存渲染结果;末尾块永不缓存——因为流式追加会改变对它的解释(未闭合的 fence、增长的列表),一旦它不再是最后一块就固定缓存。这让每 token 流式渲染的成本从"全文档"降到"仅末尾块"。

还有两个细节:CURSOR_MARKER = "\x1b_pi:c\x07"tui.ts:122-128)用终端忽略的零宽 APC 序列标记硬件光标位置,让 CJK 输入法候选窗口能定位到正确位置;stdin 缓冲层把批量字节按转义序列边界切分,保证组件收到单条完整序列。


五、Headless 与协议:JSON / RPC 模式的设计取舍

modes/ 目录下 print / json / rpc / acp / daemon 五种模式共用同一套 AgentConnection 契约和 session_event,区别只在"如何消费事件"(渲染 UI / 打印文本 / 序列化 JSONL)。这让 headless 集成零额外成本。

rpc 模式的一个细节很能体现工程严谨性:JSONL 帧解析刻意不用 Node 的 readlinerpc/jsonl.ts:19-25),因为 readline 会按额外 Unicode 分隔符拆分,破坏严格 JSONL 帧;改用 StringDecoder + indexOf("\n") 逐块扫描、跨 chunk 不重复拼接(避免大记录 O(n²))。RPC 模式下 promptResponsePending 缓冲标志保证 prompt 期间的事件不会在响应前乱序。


六、关键设计决策与亮点

把全文的设计选择收敛成五个可迁移的工程结论:

  1. 双语言分工的边界要清晰:“Python 薄封装,宿主重逻辑"这条线画得干净利落。模型要写的东西必须简单、可 import、可 await;凡是涉及授权/并发/持久化/凭据的逻辑,全部收进 TypeScript 宿主。两边各取所长。
  2. 协议层防劫持request_type 放 payload 末尾防路由覆盖(__init__.py:138-139),baseline 比对防 refine 并发覆盖(refinement.ts:737)。“不可信输入"不是一句口号,而是落在每一处边界上。
  3. 错误分类决定策略:可重试错误 vs 上下文溢出分流——重试解决临时故障,compaction 解决结构性容量问题,绝不混为一谈。
  4. 为流式而生的 UI 引擎:16ms 合并渲染、差分 patch、保留视口、按块缓存 Markdown——这些不是炫技,而是"每 token 渲染一次"场景下的生存需求。
  5. 进程隔离是生命周期手段,不是安全手段:Worker/Kernel 独立进程用于故障隔离与恢复,但 README 反复警示它们与客户端同权限、不是沙箱。诚实标注安全边界,这个姿态值得所有 agent 项目学习。

七、部署与运行说明

安装部署流程

看图重点:一键脚本下载 → SHA-256 校验 → npm 全局安装 → 内核自举 → 启动登录,以及后台会话管理能力。

macOS / Linux 安装:

curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh

安装器会下载版本化 tarball、校验 SHA-256、安装 prime-agent 命令,并准备 IPython 运行时。首次启动在目标目录运行 prime-agent,然后 /login 选择订阅或 API-key provider。常用运维命令:

prime-agent agents               # 浏览运行中/空闲/已保存的会话
prime-agent attach <agent>       # 重新挂接运行中的会话
prime-agent --resume <path|id>   # 恢复已保存会话
prime-agent status               # 检查后台服务状态
prime-agent doctor [--fix]       # 检查或修复后台服务
prime-agent shutdown [--force]   # 停止所有 agent/worker/后台服务

分发形态有两种:npm 包(含 tsx 开发模式与 esbuild 预构建 bundle),以及 bun build --compile 产出的单文件二进制(启动比 tsx 快约 3 倍)。从源码运行则是 npm install && ./prime-agent.sh

从源码构建会执行 npm run build(按 tui → ai → agent → coding-agent 顺序),coding-agent 的 build 会把 Python 运行时、skills、TUI 主题资源一并拷贝进 dist/——一个包交付全部运行时

安全提醒:Prime Agent 会用你的用户权限执行模型生成的 Python 和项目命令。请使用可检查/可恢复的克隆或工作区,只信任可信的仓库、指令与技能;不可信代码务必放进外部沙箱。


八、总结

Prime Agent 给我们的最大启发不是某个具体算法,而是一种工程气质:把"智能体"从"会调 API 的循环"提升为"可编程、可持久、可自我改进的运行时”。

  • 可编程:一切皆程序,子代理是函数调用,上下文是变量。
  • 可持久:JSONL transcript、harness 状态、快照回滚,让工作上下文活过窗口。
  • 可自我改进/refine 用证据支撑的小更新沉淀经验,不动不可变的基础 prompt。

当然,它也有明确的取舍:进程隔离不等于安全沙箱,双语言栈带来运维复杂度,约 1.1 万行的 agent-session.ts 也是明显的"上帝类”。但瑕不掩瑜——对于一个把"长任务、后台化、自改进"当一等公民的 agent 来说,这套架构是一份极好的工程参考。

如果你在构建自己的 agent,最值得带走的三个点:边界清晰的双语言分工、协议层的不可信输入防御、以及"错误分类决定处理策略"的决策框架。

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付