一、引言:一个"把智能体当编程范式"的项目
如果你用过 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 负责"被模型驱动",持久化负责"活过会话"。
看图重点:左侧从上到下是 Client → AgentConnection → Daemon Supervisor → Session Worker(含 Root AgentSession、Scheduler、IPython Kernel、RLM 子运行);右侧是 Model Providers 与持久化两列支撑。
逐层来看:
- Client 层(
packages/tui+modes/):交互式 TUI、print/json/rpc 等 headless 客户端。客户端不拥有执行权——它只渲染、接收键盘输入、维护本地 UI 偏好。 - AgentConnection(
agent-connection/types.ts):客户端侧的执行边界契约。TUI 只依赖这个接口,不直接碰AgentSessionRuntime。架构文档特别强调它"刻意保持为本地客户端契约",为将来接入远程适配器预留了边界。 - Daemon Supervisor:负责会话发现、命令路由、附件管理、worker 健康检查和跨 agent 消息投递。Session worker 与 kernel 是独立进程——这是生命周期与故障的隔离手段,但 README 明确警示:这不是安全沙箱,worker/kernel 与客户端同权限运行。
- Session Worker:一个 worker 拥有一个根会话树——
AgentSessionRuntime(生命周期外壳)、AgentSession(全部执行逻辑)、Scheduler、根 IPython Kernel,以及所有 RLM 子运行。 - 持久化: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 内核(模型可执行的薄封装),中间是 Jupyter comm 桥。
分工原则是:Python 侧只做"模型可执行的薄封装",所有重逻辑都在 TypeScript 宿主侧。
- TypeScript 侧:凭据管理(
env-api-keys.ts、auth.json)、子代理执行(rlm-runtime.ts→runRlmChild())、上下文压缩(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.py用tyro让技能 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-completions、anthropic-messages、openai-responses、google-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 的 AsyncIterable:push() 逐个投递事件,[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,压缩才是正解。
AgentSessionRuntime(agent-session-runtime.ts)则是生命周期外壳:newSession / switchSession / fork / importFromJsonl 都走同一套"teardown → replace → finish"模式,配合 session-lease.ts 的进程间租约防止多进程并发写同一会话文件。
4.3 Python 内核与 typed host request 桥
这是全项目最精妙的部分。TS 与 Python 之间的通信基于 Jupyter comm + ZeroMQ,comm target 固定为 "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 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.py 的 HarnessState),而校验与应用的策略逻辑在 TS 侧(refinement/refinement.ts)——又一次体现了"Python 薄封装、TS 重逻辑"的分工。
看图重点:轨迹 → 模型提议 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——组件不需要知道终端坐标。
性能优化贯穿三层:
- 渲染节流:
requestRender()+ 16ms 最小渲染间隔(MIN_RENDER_INTERVAL_MS),高频的流式 token 事件只在每帧末合并重绘一次。 - 差分重绘:对比上一帧与当前帧的行,只重绘
firstChanged → lastChanged区间;用\x1b[?2026h/l(synchronized output)包裹整个重绘过程防闪烁。 - 保留视口:长会话流式更新时,如果改动发生在视口上方,走
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 的 readline(rpc/jsonl.ts:19-25),因为 readline 会按额外 Unicode 分隔符拆分,破坏严格 JSONL 帧;改用 StringDecoder + indexOf("\n") 逐块扫描、跨 chunk 不重复拼接(避免大记录 O(n²))。RPC 模式下 promptResponsePending 缓冲标志保证 prompt 期间的事件不会在响应前乱序。
六、关键设计决策与亮点
把全文的设计选择收敛成五个可迁移的工程结论:
- 双语言分工的边界要清晰:“Python 薄封装,宿主重逻辑"这条线画得干净利落。模型要写的东西必须简单、可 import、可 await;凡是涉及授权/并发/持久化/凭据的逻辑,全部收进 TypeScript 宿主。两边各取所长。
- 协议层防劫持:
request_type放 payload 末尾防路由覆盖(__init__.py:138-139),baseline 比对防 refine 并发覆盖(refinement.ts:737)。“不可信输入"不是一句口号,而是落在每一处边界上。 - 错误分类决定策略:可重试错误 vs 上下文溢出分流——重试解决临时故障,compaction 解决结构性容量问题,绝不混为一谈。
- 为流式而生的 UI 引擎:16ms 合并渲染、差分 patch、保留视口、按块缓存 Markdown——这些不是炫技,而是"每 token 渲染一次"场景下的生存需求。
- 进程隔离是生命周期手段,不是安全手段: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,最值得带走的三个点:边界清晰的双语言分工、协议层的不可信输入防御、以及"错误分类决定处理策略"的决策框架。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付