拆解 prime-agent 系列——(2)Python runtime 与 kernel

一、引言:Python REPL 解决什么

pi 的工具调用是天然无状态的------read("file.ts") 读文件、bash("git status") 执行命令,两次调用之间没有共享状态。agent 要做"读文件 → 解析 → 循环处理每行 → 写回"这种组合操作,必须拆成多轮:第一轮 read 拿到内容,LLM 在 context 里看到内容后,第二轮 edit 写回。中间结果靠 LLM 在 context 里"人肉传递"------既浪费 token,又容易出错。

prime-agent 引入持久化 Python REPL------LLM 生成的 Python 代码在一个持久 REPL 里执行。上一轮 content = open("file.ts").read() 定义的变量,下一轮 print(content[:100]) 直接可用。这让 agent 从"每次工具调用独立"变成"在持久编程环境里工作"。

二、Python REPL 执行模型

REPL 是 prime-agent 最精妙的设计。pi 的 agent 是"LLM 调工具"------每个工具调用是独立的、无状态的,agent 的能力被工具的粒度框住。prime-agent 把工具调用升级为"LLM 写代码在持久化 Python 环境里执行"------一次调用能完成任意复杂的逻辑,状态跨轮次保持,agent 的能力从"工具的组合"变成"图灵完备的编程"。

这不是简单的"加了几个工具"------是把 agent 的执行模型从"声明式工具调用"换成"命令式编程"。这个改动的影响贯穿整个 prime-agent 架构:持久化状态需要 snapshot/restore、崩溃恢复需要 owner 存活检测、子 agent 需要独立的 REPL 实例、harness 状态需要通过 host_request 反向调 TypeScript 持久化。本系列从 REPL 开始拆解------理解了 REPL,prime-agent 的其余机制(RLM 子 agent、refinement、daemon、agent 家族)都有了基础。

什么是 Python REPL

REPL 是 Read-Eval-Print Loop 的缩写------交互式编程环境。Python 自带的 REPL 就是终端里输入 python 后看到的 >>> 提示符:输入一行代码,立即执行,看到结果,再输入下一行。

python 复制代码
>>> x = 42
>>> print(x * 2)
84
>>> y = "hello"
>>> print(y.upper())
HELLO

关键特征是持久命名空间 ------第一行 x = 42 定义的变量 x,在第二行 print(x * 2) 里直接可用。REPL 不是每次输入都重新开始------所有变量、导入的模块、定义的函数都在内存里保持,直到退出。

prime-agent 把这个概念引入 agent------LLM 生成的 Python 代码在一个持久 REPL 里执行。上一轮 content = open("file.ts").read() 定义的变量,下一轮 print(content[:100]) 直接可用。这让 agent 从"每次工具调用独立"变成"在持久编程环境里工作"。

REPL 带来的优势:有状态的工具调用

pi 的工具调用是天然无状态的------read("file.ts") 读文件、bash("git status") 执行命令,两次调用之间没有共享状态。agent 要做"读文件 → 解析 → 循环处理每行 → 写回"这种组合操作,必须拆成多轮:第一轮 read 拿到内容,LLM 在 context 里看到内容后,第二轮 edit 写回。中间结果靠 LLM 在 context 里"人肉传递"------既浪费 token,又容易出错。

REPL 让工具调用天然变成有状态的------因为所有操作都在同一个 Python 命名空间里执行:

python 复制代码
# 一段代码完成"读 → 解析 → 处理 → 写回"
content = open("src/index.ts").read()
lines = content.split("\n")
fixed = [l.replace("var ", "let ") for l in lines]
open("src/index.ts", "w").write("\n".join(fixed))

不需要拆成多轮、不需要 LLM 在 context 里传递中间结果、不浪费 token。上一轮的变量下一轮还能用------content 这个变量在后续轮次里直接可用,不用重新读文件。

这个优势是结构性的------不是"REPL 比工具快一点",而是"agent 的执行模型从无状态变成有状态"。后续的 RLM 子 agent(子 agent 有自己的 REPL)、harness 状态(通过 host_request 持久化)、daemon 恢复(snapshot/restore REPL 状态)都建立在这个基础之上。

REPL 带来的劣势

有状态是优势,也是负担。pi 的工具调用无状态------read 失败了只影响这一次调用,下一轮重新 read 就行。REPL 有状态------一旦状态出了问题,影响范围更大:

状态管理复杂:REPL 的变量、导入的模块、打开的文件句柄跨轮次保持。这意味着需要序列化(snapshot)、恢复(restore)、大小限制(16MB/256MB)、原子写入(防止崩溃损坏)。pi 的工具调用不需要这些------每次调用是原子的,没有持久状态要管理。

崩溃恢复困难:pi 的 agent loop 崩溃了,重启 session 从 JSONL 恢复消息历史就行------工具没有持久状态。prime-agent 的 REPL 崩溃了,不仅要恢复消息历史,还要恢复 Python 命名空间------通过 dill 序列化的 snapshot。snapshot 可能不完整(超限的变量被跳过)、可能过时(snapshot 和最新状态不一致)、恢复后可能行为异常(某些对象不能被 dill 正确序列化)。

安全面更大 :pi 的工具是预定义的函数------read 只能读文件、bash 只能执行命令,参数有 schema 校验。REPL 执行任意 Python 代码------os.system("rm -rf /")open("file.ts").read() 走同一个通道,没有 schema 校验、没有参数白名单。能力范围更大,风险也更大。

调试更难 :pi 的工具调用有明确的输入输出------read({ path: "x.ts" })ToolResultMessage。REPL 执行一段代码,输出可能分散在 stdout / stderr / display 事件 / host_request 侧道里。如果代码有 bug,错误信息可能藏在 Python traceback 里,不像工具调用那样有结构化的 error result。

资源泄漏风险 :pi 的工具调用结束就释放资源------read 读完文件就关 fd。REPL 里 f = open("file.ts") 打开的文件句柄会一直保持,直到显式 f.close() 或 REPL 退出。agent 如果忘记关闭资源,长时间运行后可能耗尽 fd 或内存。

这些劣势不是"REPL 不好"------而是"有状态的代价"。prime-agent 用 snapshot/restore、owner 存活检测、orphan 进程回收等机制来缓解这些问题,但无法完全消除。这是架构选择的 trade-off------用更高的复杂度换取更强的执行能力。

1. 持久命名空间

repl.py(1188 行)是 prime-agent 的 Python REPL 运行时------一个通过 JSON-lines 协议和 TypeScript host 通信的持久化 Python 进程。LLM 生成的代码在这里执行,状态跨轮次保持。

REPL 的核心是一个持久的 __main__ 命名空间(ns: dict[str, Any])------所有代码在同一个命名空间里执行:

python 复制代码
# repl.py:953-980(简化)
async def _serve(queue, ns):
    while True:
        req = await queue.get()
        rtype = req.get("type")
        if rtype == "execute":
            await _handle_request(_handle_execute, req, ns)    # 在 ns 里执行
        elif rtype == "snapshot":
            await _handle_request(_handle_state, req, ns)       # 序列化 ns
        elif rtype == "restore":
            await _handle_request(_handle_state, req, ns)       # 恢复 ns
        elif rtype == "list_names":
            await _handle_request(_handle_list_names, req, ns)  # 列出 ns 里的变量
        elif rtype == "shutdown":
            return

ns 是一个普通的 Python dict------键是变量名,值是变量值。每次 execute 请求的代码都在这个 ns 里 eval/exec。上一轮 content = open("file.ts").read() 定义的 content,下一轮 print(content[:100]) 直接可用------因为两次执行用的是同一个 ns

_list_namesrepl.py:920-925)过滤出用户定义的变量------跳过 _ 开头的、跳过 _ALWAYS_SKIPrlm / mcp / bash / asyncio 等系统模块):

python 复制代码
_ALWAYS_SKIP = {"rlm", "mcp", "bash", "asyncio", "In", "Out", "get_ipython", "exit", "quit", "open"}

def _list_names(ns):
    return sorted(
        name for name in ns
        if isinstance(name, str) and not name.startswith("_") and name not in _ALWAYS_SKIP
    )

2. Cell 执行

每次 execute 请求是一个"cell"------一段 Python 代码。执行流程:

bash 复制代码
TypeScript host 发 {"type": "execute", "id": "abc123", "code": "print('hello')"}
  → Python REPL 收到
  → _handle_execute(req, ns)
  → compile(code, "<cell>", "exec")  # 编译
  → exec(code, ns)                    # 在持久 ns 里执行
  → 捕获 stdout/stderr/display 事件
  → _send({"event": "done", "id": "abc123", "status": "ok"})

cell 执行支持 top-level await ------代码里可以直接写 await asyncio.sleep(1),不需要包在 async def 里。这是通过 compile(code, "<cell>", "exec") + exec 在 asyncio 事件循环里执行的。

每个 cell 有一个唯一 ID(_current_cell)------display 事件用这个 ID 标注"这段输出是哪个 cell 产生的"。这让 TypeScript host 能把输出和请求对应起来。

3. asyncio 事件循环

REPL 跑在一个 asyncio 事件循环上(repl.py:46):

python 复制代码
_loop: asyncio.AbstractEventLoop | None = None
_serve_task: asyncio.Task[Any] | None = None

_serve 是事件循环的主任务------从队列里取请求、执行、发回结果。请求不是直接处理的------stdin 的读取在独立线程 里做(_read_requests),读到请求后通过 _loop.call_soon_threadsafe(queue.put_nowait, req) 投递到事件循环的队列:

python 复制代码
# repl.py:1042-1058(简化)
def _read_requests(stdin_fd, queue):
    with os.fdopen(stdin_fd, "rb") as stream:
        for raw in stream:                          # 阻塞读 stdin
            raw = raw.strip()
            if not raw: continue
            _handle_request_line(raw, queue)        # 解析 + 投递到队列
    # stdin 关闭 → shutdown
    _loop.call_soon_threadsafe(queue.put_nowait, {"type": "shutdown"})

为什么用独立线程读 stdin?因为 asyncio 的事件循环不能同时阻塞读 stdin 和执行 cell 代码。读 stdin 在线程里做,代码执行在事件循环里做------两者通过 asyncio.Queue 通信。

4. 中断

用户按 Ctrl-C 或 TypeScript host 发 {"type": "interrupt"} 时,REPL 需要中断正在执行的 cell(repl.py:1002-1007):

python 复制代码
if rtype == "interrupt":
    _request_interrupt(req.get("id"))
    return

中断是协作式的------不是直接 kill -9,而是发 SIGINT 信号让 asyncio 任务自己取消。_request_interrupt 设置 _pending_interrupts 标记,_serve 循环在下一次检查时取消当前 cell 的 task。

中断有一个 inflight 机制------用 _inflight set 跟踪当前正在执行的 request ID。中断时可以指定 ID(只中断某个 cell)或不指定(中断任何正在执行的 cell):

python 复制代码
# repl.py:62-63
_active: dict[str, Any] = {"task": None, "rid": None, "interrupted": False}
_inflight: set[str] = set()

5. Host Request------Python 反向调 TypeScript

REPL 不只是被动接收 TypeScript 的请求------Python 代码可以反向调 TypeScript host(repl.py:119-132):

python 复制代码
async def host_request(data: dict[str, Any]) -> dict[str, Any]:
    """Send one typed request to the host and await its raw reply dict."""
    rid = uuid.uuid4().hex
    future: asyncio.Future = _loop.create_future()
    _pending_host[rid] = future
    _send({"event": "host_request", "id": rid, "data": data})
    return await future    # 等 TypeScript 回复

host_request 让 Python 代码能调 TypeScript 的能力------如 rlm.harness.create_memory() 内部走 host_request 让 TypeScript 持久化到 session 文件。这是一个双向通信通道:

css 复制代码
TypeScript → Python: {"type": "execute", "code": "..."}
Python → TypeScript: {"event": "host_request", "data": {"type": "create_memory", ...}}
TypeScript → Python: {"type": "host_reply", "id": "...", "data": {"status": "ok"}}
Python → TypeScript: {"event": "done", "status": "ok"}

host_reply 不走 _serve 队列------它直接 resolve 对应的 future(repl.py:1008-1017),否则会死锁(cell 在等 host_reply,队列被 cell 占着):

python 复制代码
if rtype == "host_reply":
    # Bypass the FIFO queue: the awaiting cell IS the in-flight execute,
    # so a queued reply would deadlock behind it.
    _resolve_host_reply(rid, data)
    return

6. 状态快照------崩溃恢复

REPL 的状态(ns dict)需要能序列化到磁盘------session 恢复时从快照恢复变量。snapshotrestore 请求处理这个(repl.py:680-800):

python 复制代码
# snapshot:把 ns 序列化到文件
# 用 dill(不是 pickle)------dill 能序列化更多 Python 对象(如 closures、lambda)
dill.dump(payload, writer)  # 写到临时文件
os.replace(tmp, path)       # 原子替换------防止崩溃时文件损坏

# restore:从文件恢复到 ns
dill.load(open(path, "rb"))  # 读回来
ns.update(restored)          # 合并到当前 ns

关键设计:

  • 用 dill 而不是 pickle------dill 能序列化 pickle 不能处理的对象(lambda、closure、嵌套函数)
  • 原子替换 ------先写临时文件,写完后 os.replace 原子替换。崩溃时要么旧文件完整、要么新文件完整,不会出现半写的损坏文件
  • 大小限制------单变量最大 16MB,总快照最大 256MB。超限的变量被跳过并记录在 manifest 里
  • SIGINT park------快照写入期间屏蔽 SIGINT,防止中断导致文件损坏

7. Owner 存活检测

REPL 进程需要检测 TypeScript host 是否还活着------如果 host 崩溃了,REPL 应该自动退出(repl.py:1061-1099):

python 复制代码
# POSIX:检查 parent process 是否还在
def _owner_alive_posix(owner, initial_ppid):
    if initial_ppid == owner and os.getppid() != initial_ppid:
        return False    # parent 改了 → host 死了(进程被 reparent)
    try:
        os.kill(owner, 0)    # 发 signal 0 检测存活
    except ProcessLookupError:
        return False
    return True

# Windows:用 OpenProcess + WaitForSingleObject
def _wait_owner_windows(owner):
    handle = k32.OpenProcess(SYNCHRONIZE, False, owner)
    k32.WaitForSingleObject(handle, INFINITE)    # 阻塞等 host 退出

host 进程的 PID 通过环境变量 PRIME_AGENT_KERNEL_OWNER_PID 传给 REPL。如果 host 死了,REPL 检测到后自动 shutdown------清理 bash 子进程、关闭 MCP 连接、退出。

三、Python runtime

prime-agent-runtime/ 是一个独立的 Python 包(pyproject.toml + src/rlm/),通过 uv 安装到 REPL 环境里。它提供 REPL 里 LLM 能调用的 Python 模块------rlm.harness(harness 状态)、rlm.bash(shell 执行)、rlm.skill(skill CLI)、rlm.mcp(MCP 协议)、rlm 本身(子 agent spawn)。

1. init.py------RLM 入口

rlm/__init__.py(366 行)定义了 LLM 在 REPL 里直接用的顶层 API。它不是 REPL 本身------repl.py 是协议层,init.py 是"LLM 能调什么"的 API 层。

核心导出:

python 复制代码
from .bash import BashHandle, BashResult, bash
from .harness import HarnessEntry, HarnessScope, HarnessState, get_harness_state

rlm.run------spawn 子 agent(后续文章详讲)

rlm.create_session------创建独立 daemon session

RLMSpawnHandle / RLMCreateSessionHandle / RLMModel / RLMSubagent------这些 dataclass 是 host_request 返回值的类型包装。LLM 调 rlm.run("analyze deps") 后,TypeScript host 执行 spawn,返回 payload,Python 侧用 init.py 的 _spawn_handle_from_payload 解析成 RLMSpawnHandle

2. harness.py------Harness 状态管理

harness.py(838 行)是 prime-agent 的"自改进"基础设施------管理 prompt notes / memories / skills / subagent specs 四类可持久化状态。LLM 通过 rlm.harness API 在 Python REPL 里直接读写这些状态。

存储模型

四类状态用 HarnessKind 区分:

python 复制代码
HarnessKind = Literal["prompt", "memory", "skill", "subagent"]

两类 scope:

  • local :session 级------存在 session 目录的 harness/harness_state.json 里,只对当前 session 有效
  • global :跨 session------存在 ~/.prime/agent/harness/harness_state.json 里,所有 session 共享

存储格式是单个 JSON 文件:

json 复制代码
{
  "schema": 1,
  "entries": {
    "prompt": { "build_policy": { "id": "build_policy", "kind": "prompt", "title": "构建策略", "content": "用 npm run build,不要用 yarn", ... } },
    "memory": { "api_rate_limit": { "id": "api_rate_limit", "kind": "memory", "title": "API Rate Limit", "content": "每分钟 60 次", ... } },
    "skill": {},
    "subagent": {}
  },
  "refinements": [
    { "id": "refine_0001", "trigger": "构建失败", "changes": ["创建 memory: build_command"], "evidence": "npm run build 成功,yarn 失败" }
  ]
}

entries 按 kind 分组,每组是 { id: HarnessEntry } 的 dict。refinements 记录每次 /refine 的事件------改动列表 + 证据 + 结果。

HarnessEntry

每条 harness 状态是一个 HarnessEntry

python 复制代码
@dataclass
class HarnessEntry:
    id: str                    # 唯一标识(自动从 title 生成 slug)
    kind: HarnessKind          # "prompt" / "memory" / "skill" / "subagent"
    title: str                 # 人类可读标题
    content: str               # 主要内容
    path: str = "general"      # 分类路径(如 "build" / "deploy")
    scope: HarnessScope        # "local" / "global"
    reference: dict = ...      # skill 的 Python import 信息
    arguments: dict = ...      # skill 的参数 schema
    metadata: dict = ...       # 任意元数据
    source: str = "agent"      # 谁创建的------"agent"(LLM 创建)或 "user"(用户创建)
    created_at: str            # 创建时间
    updated_at: str            # 最后更新时间
    version: int = 1           # 版本号------每次 update +1

version 字段支持回滚------/refine 可以用旧版本号恢复之前的 entry。

referencearguments 只对 skill 类型有意义------skill 是可执行的 Python 包,reference 记录 import 路径(如 { "type": "python", "import": "my_skill" }),arguments 记录参数 schema。

CRUD 操作

HarnessState 类提供完整的 CRUD(harness.py:144-787):

python 复制代码
class HarnessState:
    def create_memory(self, title, content, *, global_=False, ...): ...
    def update_memory(self, id, title, content, *, global_=False, ...): ...
    def delete_memory(self, id, *, global_=False, ...): ...
    def create_prompt_note(self, title, content, *, global_=False, ...): ...
    def create_skill(self, title, content, *, reference, arguments, global_=False, ...): ...
    def create_subagent(self, title, content, *, global_=False, ...): ...
    # ... 每种 kind 都有 create / update / delete
    def list(self, kind=None, *, global_=False): ...
    def get(self, kind, id, *, global_=False): ...
    def overview(self, *, global_=False): ...

每种 kind 有专用的 create_xxx / update_xxx / delete_xxx 方法,但内部都委托给统一的 create / update / delete

python 复制代码
def create(self, kind, title, content, *, id=None, path="general", global_=False, ...):
    # 1. 解析 global_ 参数 + scope prefix
    id, global_ = _strip_scope_prefix(id, global_)

    # 2. 如果 global_=True,委托给全局 HarnessState
    if target := self._global_target(global_, kwargs):
        return target.create(kind, title, content, ...)

    # 3. 同步磁盘------防止其他进程(如 /refine)改了文件
    self._sync_from_disk()

    # 4. 检查是否已存在(create 不允许覆盖)
    entry_id = id or _slug(title, kind)
    if entry_id in self.entries[kind]:
        raise ValueError(f"{kind} entry {entry_id!r} already exists")

    # 5. 写入 + 持久化
    return self._upsert(kind, title, content, id=entry_id, ...)

原子写入

save() 用原子替换防止崩溃损坏(harness.py:287-319):

python 复制代码
def save(self):
    # 1. 序列化到临时文件
    temp_path = target_path.with_name(f"{target_path.name}.{os.getpid()}.{uuid4().hex}.tmp")
    with open(temp_path, "w") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)

    # 2. 保留原文件权限
    os.chmod(temp_path, existing_mode)

    # 3. 原子替换
    os.replace(temp_path, target_path)

    # 4. 清理临时文件(如果 replace 失败)
    temp_path.unlink(missing_ok=True)

和 REPL 的 snapshot 一样的策略------先写临时文件,os.replace 原子替换。崩溃时要么旧文件完整、要么新文件完整。

跨进程同步

_sync_from_diskharness.py:189-199)检测文件是否被其他进程修改:

python 复制代码
def _sync_from_disk(self):
    """Reload if another process rewrote the state file since we last touched it."""
    if self._disk_mtime() != self._loaded_mtime:
        self.load()

用文件的 mtime(修改时间)判断------如果磁盘上的 mtime 和内存里记录的不一致,说明另一个进程(如 TypeScript 侧的 /refine 命令)改了文件,重新加载。

这解决了"Python REPL 和 TypeScript host 同时操作 harness 文件"的并发问题------Python 侧每次 CRUD 前先检查 mtime,确保不会用内存里的旧状态覆盖磁盘上的新状态。

RefinementEvent

每次 /refine 记录一个 RefinementEventharness.py:116-124):

python 复制代码
@dataclass
class RefinementEvent:
    id: str                    # 如 "refine_0001"
    trigger: str               # 触发原因------如"构建失败后发现 npm run build 才是对的"
    changes: list[str]         # 改了什么------如["创建 memory: build_command"]
    evidence: str = ""         # 证据------如"npm run build 成功,yarn 失败"
    outcome: str = ""          # 结果------如"后续构建都成功了"
    created_at: str            # 时间

这让 harness 状态的变更可追溯------overview() 方法列出最近 5 次 refinement 事件,用户可以看到 agent 学了什么、为什么学、学了之后效果如何。

overview------harness 状态总览

overview()harness.py:740-787)生成人类可读的 harness 状态摘要:

python 复制代码
def overview(self, *, global_=False):
    lines = [
        f"Harness state ({self.scope}): {self.file_path}",
        "Call contract: installed Python skills use await <skill_import>(...) ...",
    ]
    for kind in _KINDS:
        records = self.list(kind)[:max_entries_per_kind]
        lines.append(f"{kind}: {len(self.entries[kind])}")
        for entry in records:
            summary = entry.content.strip().replace("\n", " ")
            if len(summary) > 120:
                summary = f"{summary[:117]}..."
            lines.append(f"  - [{entry.scope}:{entry.id}] {entry.title} ({entry.path}, v{entry.version}): {summary}")
    # ... 最近 5 次 refinement 事件
    return "\n".join(lines)

LLM 在 REPL 里调 print(rlm.harness.overview()) 看到当前所有 harness 状态------知道哪些 memory / skill / prompt note 已经积累了、版本号是多少、最近 refine 了什么。

3. bash.py------持久化 bash handle

bash.py(1104 行)是 prime-agent 的 shell 执行模块。它和 pi 的 bash 工具完全不同------不是"执行命令返回结果",而是"返回一个 live handle,命令在后台异步执行"。

BashHandle------异步命令 handle

核心抽象是 BashHandle------一个对正在运行的 shell 命令的引用(bash.py:216-224):

python 复制代码
class BashHandle:
    """Live handle to a shell command; await it for the BashResult.

    A handle awaited before any other API use (the `await bash(cmd)` one-shot
    form, including `h = bash(cmd)` awaited immediately) owns the command:
    cancelling that await kills the process group. Touching .pid/.running/
    .output()/.tail()/.poll()/.kill() first marks the handle as a background
    handle; later awaits only wait and cancelling them leaves it running.
    """

两种使用模式:

python 复制代码
# 模式 1:同步等待(和 pi 的 bash 工具类似)
result = await bash("git status")
print(result.stdout)

# 模式 2:异步后台(pi 做不到)
handle = bash("npm test")        # 立即返回,命令在后台跑
# ... 做其他事 ...
result = await handle             # 等命令完成
print(result.exit_code)

# 模式 3:多个命令并行(pi 做不到)
h1 = bash("npm test")
h2 = bash("npm run lint")
r1 = await h1
r2 = await h2

BashResult 是命令完成后的结果:

python 复制代码
@dataclass(frozen=True)
class BashResult:
    exit_code: int
    output: str        # stdout + stderr 合并
    duration: float    # 执行时长(秒)

输出捕获------BoundedBuffer

_BoundedBufferbash.py:168-213)是输出捕获的核心------保留开头 512KB + 结尾 1.5MB,中间丢弃:

python 复制代码
class _BoundedBuffer:
    _HEAD_CAP = 512 * 1024    # 512KB
    _TAIL_CAP = 3 * 512 * 1024  # 1.5MB

    def write(self, chunk):
        if len(self._head) < _HEAD_CAP:
            # 先填 head
            take = _HEAD_CAP - len(self._head)
            self._head.extend(chunk[:take])
            chunk = chunk[take:]
        if not chunk: return
        # head 满了 → 写 tail(滚动队列)
        self._tail.append(chunk)
        while self._tail_size > _TAIL_CAP:
            # tail 超限 → 丢最旧的
            ...

    def text(self):
        if not self._dropped:
            return (head + tail).decode("utf-8")
        # 有丢弃 → 插入标记
        return head + f"\n... [{dropped} bytes dropped] ...\n" + tail

为什么保留头 + 尾?因为命令输出的开头通常有命令回显、编译器版本等信息,结尾有错误摘要、exit code。中间的大量 log 不重要。这和 pi 的 bash 工具的 OutputAccumulator 思路类似,但 prime-agent 的实现更精细------用滚动队列而不是简单截断。

完成检测------completion marker

bash.py 不用 process.wait() 检测命令完成------而是用一个特殊的 completion marker 注入到输出流里(bash.py:275-285):

python 复制代码
completion_token = secrets.token_hex(32)
self._completion_marker = (
    _COMPLETION_PREFIX + completion_token.encode("ascii") + _COMPLETION_SUFFIX
)
# marker 格式:\x1eprime-agent-complete:<token>\x1f

shell 脚本在命令执行完后输出这个 marker + exit code。_consume_outputbash.py:441-463)在输出流里扫描 marker:

python 复制代码
def _consume_output(self, chunk):
    marker = self._completion_marker
    data = self._completion_pending + chunk
    marker_at = data.find(marker)
    if marker_at >= 0:
        # 找到 marker → 命令完成
        self._buffer.write(data[:marker_at])
        self._completion_output = self._buffer.text()
        self._completion_terminal.set()    # 通知等待者
        self._buffer.write(data[marker_at + len(marker):])  # marker 后面的输出

为什么不用 process.wait()?因为 shell 的 cmd &(后台命令)会立即返回------shell 进程退出但子进程还在跑。completion marker 让 bash.py 知道"前台命令完成了",即使 shell 还在等后台子进程。

token 用 secrets.token_hex(32) 生成------防止命令输出里碰巧出现 marker 字符串导致误判。marker 分两半注入(前半 + 后半),防止 echo 泄漏。

进程组管理

bash.pystart_new_session=True 创建独立的进程组(bash.py:296-304):

python 复制代码
self._proc = subprocess.Popen(
    [_shell(), "-c", script],
    cwd=os.getcwd(),
    env=_child_env(),
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    start_new_session=True,       # 独立进程组
    stdin=status_write,           # status channel
)

独立进程组让 kill() 能杀整个命令树------git status 可能 spawn 子进程(如 pager),进程组 kill 确保子进程也被清理。

Windows 没有进程组------用 Job Object(_winjob.py)实现进程树包含。kill()TerminateJobObject 终止整个 job。

Orphan 回收

如果 cell 结束但 bash 命令还在跑,命令变成 orphan。bash.py 有三层防护:

  1. _live_handles setbash.py:56)------所有活跃的 BashHandle 都在集合里。_kill_live_handles() 在 atexit 时清理
  2. orphan-process-journal (TypeScript 侧)------_record_journal(self._pid, active=True) 在创建时记录 PID。TypeScript host 的 reaper 定期检查 journal,清理孤儿进程
  3. owner 存活检测 ------如果 host 崩溃,REPL 的 owner 检测触发 shutdown,_kill_live_handles() 清理所有 bash 子进程

PID 复用防护------journal 用 processStartId(来自 session-lease.ts)而不只是 PID,防止 PID 被复用后误杀新进程。

Cell 关联

BashHandle 知道自己是哪个 cell 创建的(bash.py:228-231):

python 复制代码
completion_context = _current_cell_completion_context()
self._creating_cell_finished = completion_context[0] if completion_context else None
self._creating_cell_task = completion_context[1] if completion_context else None

如果 cell 被中断(用户按 Ctrl-C),相关的 bash 命令也会被取消------但只有"foreground" handle(被 cell 直接 await 的)会被取消。"background" handle(cell 没等它)继续跑,等下一个 cell 或 atexit 清理。

这个区分通过 _released 标记实现------如果调了 .pid / .running / .output() 等属性,handle 变成 background,后续的 await 不会 kill 它。

4. skill.py------Python skill CLI

skill.py(37 行)让 Python skill 能作为 CLI 命令执行:

python 复制代码
def cli():
    prog = Path(sys.argv[0]).stem        # 从命令名推断模块名
    module = __import__(prog)            # 动态导入 skill 模块
    run = getattr(module, "run", None)   # 找 run() 函数
    asyncio.run(run_cli(run, prog=prog)) # 用 tyro 解析参数 + 执行

prime-agent 的 skill 是可执行的 Python 包 ------不是 pi 的静态 Markdown。skill 暴露一个 run() 函数,tyro 自动从函数签名生成 CLI 参数解析。这让 skill 既是 Python 模块(在 REPL 里 import 调用)又是 CLI 命令(在终端执行)。

5. mcp.py + mcp_base.py------MCP 协议

mcp.py(658 行)+ mcp_base.py(337 行)让 Python REPL 能连接 MCP server。mcp.py 提供 connect() / close() / call_tool() 等 API,通过 host_request 让 TypeScript 侧的 MCP manager 管理连接生命周期。Python 侧只是调用接口------真正的 MCP 通信在 TypeScript 侧的 mcp/mcp-manager.ts 里。

四、kernel------TypeScript 侧

kernel/ 是 TypeScript 侧管理 Python REPL 进程的模块。Python runtime(repl.py)是被管理的子进程,kernel/ 是管理者------负责启动 Python 环境、spawn REPL 进程、通过 JSON-lines 协议通信、管理执行队列、处理中断、做状态快照。

1. bootstrap.ts------Python 环境安装

bootstrap.ts(962 行)负责"确保 Python 环境就绪"------安装 Python 3.11 + uv + prime-agent-runtime + 预装包。

核心函数 ensureKernelPythonbootstrap.ts:109+):

typescript 复制代码
async function ensureKernelPython(options): Promise<string> {
  // 1. 检查 uv 是否已安装------如果没有,自动安装
  // curl -LsSf https://astral.sh/uv/install.sh | sh

  // 2. 创建虚拟环境(venv)
  // 用 uv venv 创建 Python 3.11 的虚拟环境

  // 3. 安装 prime-agent-runtime
  // uv pip install prime-agent-runtime

  // 4. 安装预装包(requests / httpx / pyyaml / pandas / numpy / scipy / ...)
  // DEFAULT_RLM_EXTRA_PACKAGES

  // 5. 安装 Python skills(如果有)
  // 递归解析 skill 依赖,用 uv pip install 安装

  // 6. 运行 RUNTIME_READY_CHECK------验证 runtime API 完整
  // 检查 rlm.spawn / rlm.harness.create_memory / mcp.list_tools 等方法存在

  // 7. 写 .bootstrap-version 文件------记录版本,下次跳过安装
}

关键设计:

  • uv 包管理 ------用 uv 而不是 pip。uv 比 pip 快 10-100 倍,且支持 uv venv 创建虚拟环境
  • 版本检查 ------.bootstrap-version 文件记录 schema + runtime 版本 + 预装包列表。如果文件存在且版本匹配,跳过安装。否则重新安装
  • 预装包 ------DEFAULT_RLM_EXTRA_PACKAGES 包含 12 个常用包(requests / httpx / pyyaml / pandas / numpy / scipy / bs4 / lxml / pydantic / tyro 等),让 LLM 直接 import pandas 不需要安装
  • READY_CHECK ------一个 Python 单行脚本,检查 rlm.spawn / rlm.harness.create_memory / mcp.list_tools 等 API 都存在。如果 runtime 版本不匹配,RUNTIME_READY_CHECK 抛异常,bootstrap 重新安装
  • Python skill 依赖解析 ------normalizePythonSkills 递归读 skill 的 pyproject.toml,解析 [project.dependencies],把依赖也装上

2. repl-manager.ts------REPL 进程管理

repl-manager.ts(1694 行)是 kernel 的核心------ReplKernelManager 类管理一个 Python REPL 子进程的完整生命周期。

启动

start()doStart()repl-manager.ts:296-382):

typescript 复制代码
async doStart(options) {
  // 1. 确保 Python 环境就绪
  python = await ensureKernelPython({ pythonSkills: this.options.pythonSkills });

  // 2. spawn Python 子进程
  const child = spawnHidden(python, ["-m", "rlm.repl"], {
    cwd: this.options.cwd,
    env: {
      ...process.env,
      PRIME_AGENT_KERNEL_OWNER_PID: String(process.pid),  // 让 REPL 检测 host 存活
    },
    stdio: ["pipe", "pipe", "pipe"],
  });

  // 3. 等 ready 事件
  const protocol = await this.waitForReady(child);

  // 4. 检查协议版本
  if (protocol !== REPL_PROTOCOL_VERSION) {
    throw new Error("Kernel runtime speaks protocol X, expected Y");
  }

  this.state = "running";
}

启动流程:安装 Python → spawn python -m rlm.repl → 等 ready 事件 → 检查协议版本 → 标记 running。

spawnHidden 创建一个"隐藏"子进程------不继承终端,不显示窗口。PRIME_AGENT_KERNEL_OWNER_PID 让 REPL 知道谁是 host------host 死了 REPL 自动退出。

事件处理

wireChildrepl-manager.ts:389-512)绑定子进程的 stdout/stderr/exit 事件:

typescript 复制代码
private wireChild(child: ChildProcess) {
  // stdout → JSON-lines 解析
  child.stdout.on("data", (buf) => {
    buffered += decoder.write(buf);
    // 按换行符拆分
    while ((newline = buffered.indexOf("\n")) !== -1) {
      const line = buffered.slice(0, newline);
      buffered = buffered.slice(newline + 1);
      const event = JSON.parse(line);           // 解析 JSON
      const invalidReason = invalidProtocolFrameReason(event);
      if (invalidReason) {
        this.failProtocolFrame(child, invalidReason);  // 协议错误 → 修复
        return;
      }
      this.handleEvent(event);                   // 分派事件
    }
  });

  // stderr → 日志(有大小限制)
  child.stderr.on("data", (buf) => {
    this.appendKernelStderrText(decoder.write(buf));
    writeFullySync(stderrLog.fd, buf);           // 写到磁盘日志
  });

  // exit → 清理
  child.on("exit", (code, signal) => {
    this.state = "shutdown";
    liveKernels.delete(this);
    this.cleanupResources();
  });
}

stdout 是协议通道------每行一个 JSON 对象。stderr 是诊断通道------写到 ~/.prime/agent/logs/kernel-stderr.log(有 5MB 大小限制,超限轮转)。

执行队列

REPL 一次只执行一个 cell------executionQueue 序列化所有 execute 请求(repl-manager.ts:185):

typescript 复制代码
private executionQueue: Promise<unknown> = Promise.resolve();

async execute(code: string, options?: ExecuteOptions): Promise<ExecuteResult> {
  // 排队------等前一个 execute 完成
  const result = this.executionQueue.then(() => this.doExecute(code, options));
  this.executionQueue = result.then(() => undefined, () => undefined);
  return result;
}

为什么串行?因为 REPL 的命名空间是共享的------两个 cell 并发执行会互相干扰(一个 cell 改了变量,另一个 cell 看到的是中间状态)。串行保证每个 cell 看到的是上一个 cell 执行完后的完整状态。

中断

TypeScript host 发 {"type": "interrupt"} 给 REPL 进程。但如果 REPL 没响应(如死循环),repl-manager 有超时机制:

typescript 复制代码
// 等中断响应
const interrupted = await raceWithTimeout(
  this.waitForInterrupt(requestId),
  KERNEL_BUSY_INTERRUPT_INTERVAL_MS
);
if (!interrupted) {
  // 超时------检查 REPL 是否还活着
  if (await this.isBusy()) {
    throw new KernelBusyAfterInterruptError();
  }
}

如果中断超时且 REPL 仍然 busy,repl-manager 可以 kill 子进程并重启------但这是最后手段,会丢失 REPL 状态。

协议修复

如果 stdout 收到无法解析的 JSON 或未知事件类型,failProtocolFramerepl-manager.ts:514-553)触发协议修复:

typescript 复制代码
private failProtocolFrame(child: ChildProcess, diagnostic: string) {
  this.appendKernelDiagnostic(diagnostic);
  this.readyDeferred?.reject(error);
  this.rejectActiveExecution(error);

  // 不是 shutdown 状态 → 尝试修复
  if (this.state === "running") {
    const owner = { superseded: false };
    this.protocolRepairOwner = owner;
    this.repairProtocolChild(child, owner);   // kill + 重启 + restore snapshot
  }
}

修复流程:kill 损坏的子进程 → 重新 spawn → 从 snapshot 恢复状态。如果 snapshot 本身有问题(恢复后仍然 corruption),标记 snapshot 为"可疑",不再尝试恢复。

状态快照

state-snapshot.ts(47 行)定义快照的接口。实际的 snapshot/restore 请求通过协议发给 Python REPL 执行(repl.py_handle_state)。

repl-manager 有一个 debounced 自动快照机制------cell 执行完后延迟一段时间自动 snapshot,防止长时间运行后崩溃丢失状态:

typescript 复制代码
private scheduleSnapshot(): void {
  if (this.snapshotTimer) return;
  this.snapshotTimer = setTimeout(() => {
    this.snapshotTimer = undefined;
    void this.snapshot();   // 发 {"type": "snapshot", ...} 给 REPL
  }, DEFAULT_SNAPSHOT_DEBOUNCE_MS);
}

五、通信协议

TypeScript host 和 Python REPL 通过 JSON-lines 协议通信------每行一个 JSON 对象,通过子进程的 stdin/stdout 传输。

协议版本

ini 复制代码
REPL_PROTOCOL_VERSION = 3

启动时握手------REPL 发 {"event": "ready", "protocol": 3},host 检查版本匹配。不匹配则报错并要求更新 runtime。

TypeScript → Python:请求

type 字段 做什么
execute id, code 在 REPL 命名空间里执行一段 Python 代码
snapshot id, path, manifest_path 序列化 REPL 命名空间到文件
restore id, path 从文件恢复 REPL 命名空间
list_names id 列出用户定义的变量名
interrupt id? 中断正在执行的 cell(id 可选------不指定则中断任何)
host_reply id, data 回复 Python 的 host_request
shutdown id? 关闭 REPL

每条请求有一个唯一 id------REPL 在回复 done 事件时带上同一个 id,让 host 对应请求和响应。

Python → TypeScript:事件

event 字段 做什么
ready protocol 启动完成,协议版本握手
stdout id, text cell 的 stdout 输出
stderr id, text cell 的 stderr 输出
result id, data cell 的 display 输出(MIME 类型 → JSON payload)
display id, data 实时 display 事件(如图表、HTML)
host_request id, data Python 反向调 TypeScript host
error id, ename, evalue, traceback 执行错误
done id, status, ... cell 执行完成(status: "ok" / "error")

done 事件是每条请求的终止信号------host 收到 done 后 resolve 对应的 Promise。done 的额外字段取决于请求类型(如 snapshot 的 done 带 saved / skipped / bytes)。

完整往返

css 复制代码
1. TypeScript 发: {"type": "execute", "id": "abc123", "code": "print('hello')"}
2. Python 执行中 → 发: {"event": "stdout", "id": "abc123", "text": "hello\n"}
3. Python 完成 → 发: {"event": "done", "id": "abc123", "status": "ok"}
4. TypeScript 收到 done → resolve execute 的 Promise

带 host_request 的往返(Python 反向调 TypeScript):

css 复制代码
1. TypeScript 发: {"type": "execute", "id": "cell1", "code": "rlm.harness.create_memory(...)"}
2. Python 执行 create_memory → 内部调 host_request → 发: {"event": "host_request", "id": "req1", "data": {"type": "create_memory", ...}}
3. TypeScript 收到 host_request → 执行 harness.create_memory → 发: {"type": "host_reply", "id": "req1", "data": {"status": "ok"}}
4. Python 收到 host_reply → resolve future → create_memory 返回
5. Python 完成 → 发: {"event": "done", "id": "cell1", "status": "ok"}
6. TypeScript 收到 done → resolve execute 的 Promise

关键点:host_reply 不走 _serve 队列------它直接 resolve 对应的 future(repl.py:1008-1017),否则会死锁(cell 在等 host_reply,队列被 cell 占着)。

中断协议

css 复制代码
1. TypeScript 发: {"type": "interrupt", "id": "abc123"}  (指定 ID)或 {"type": "interrupt"}  (任意)
2. Python 收到 → _request_interrupt → 发 SIGINT 给当前 cell 的 task
3. Cell 被取消 → 发: {"event": "done", "id": "abc123", "status": "error", "reason": "interrupted"}
4. TypeScript 收到 done(error) → reject execute 的 Promise

中断是协作式的------不是 kill -9,而是 SIGINT 让 asyncio 任务自己取消。如果 cell 在等 IO(如 await bash("sleep 100")),bash handle 也会被取消。

六、Q&A

Q1:Harness 中的 global state 什么时候被更新,是显式的还是隐式的?

global state 的更新是显式 的------通过 global_=True 参数。

每个 CRUD 操作(create_memory / update_memory / create_skill 等)都接受一个 global_: bool = False 参数:

python 复制代码
# harness.py:549-560
def create_memory(self, title, content, *, global_=False, ...):
    return self.create("memory", title, content, global_=global_, ...)

# LLM 在 REPL 里显式指定
rlm.harness.create_memory("构建命令", "npm run build", global_=True)   # → 全局
rlm.harness.create_memory("当前分支", "feature/auth", global_=False)   # → session 级(默认)

默认 global_=False------写入 session 级的 harness/harness_state.json。只有显式传 global_=True 才写入全局的 ~/.prime/agent/harness/harness_state.json

/refine 命令更新 harness 时也是显式的------它回顾 trajectory 后决定哪些更新写到 local、哪些写到 global,每条更新都带 global_ 参数。

没有"自动从 local 提升到 global"的隐式机制------LLM 或 /refine 必须明确指定 scope。这避免了"session 级的临时笔记意外污染全局"的问题。

Q2:HarnessKind 是 prime-agent 新增的机制吗?pi 中 prompt / memory / skill 的存储是怎样的?

是的,HarnessKind 是 prime-agent 新增的机制。pi 没有这个概念。

pi 的存储模型:

资源 pi 怎么存 pi 怎么更新
skill .pi/skills/xxx/SKILL.md(Markdown 文件) 用户手动写文件
prompt .pi/prompts/xxx.md(Markdown 文件) 用户手动写文件
extension .pi/extensions/xxx.ts(TypeScript 文件) 用户手动写文件
memory 没有 没有
subagent spec 没有 没有

pi 的所有资源都是静态文件------用户写好,agent 读。agent 不能自己创建或更新这些文件。

prime-agent 的 HarnessKind 把四种资源统一成一个结构化 JSON 存储harness_state.json),agent 可以通过 Python API 自己 CRUD:

HarnessKind 存什么 pi 有对应物?
prompt prompt note------行为策略补充 pi 的 prompt template 是文件,不能自改进
memory 事实/决策/偏好/失败教训 pi 完全没有
skill 可执行 Python skill spec(带 import 信息) pi 的 skill 是静态 Markdown,不能执行
subagent 可复用的子 agent 委托规格 pi 完全没有

关键差异:

  • pi:资源是文件,agent 只读不写。要改 skill,用户手动编辑 SKILL.md
  • prime-agent :资源是 JSON 里的 entry,agent 可以 rlm.harness.create_memory(...) / update_skill(...) / delete_prompt_note(...) 自己改。/refine 命令让 agent 基于工作轨迹自动更新这些 entry

这就是"Self-Improving"的落地------pi 的 skill 永远不变,prime-agent 的 harness 状态随 agent 的经验增长而演化。

Q3:HarnessEntry 中 metadata 是由谁注入的?metadata 是有限的(可穷举的)还是由 LLM 任意写入的(无限的)?

谁注入

两条路径,通过 source 字段区分来源:

注入方 路径 source
LLM 直接调 CRUD API rlm.harness.create_memory(..., metadata={...})create()_upsert() "agent"(默认)
/refine 命令 refiner LLM 生成 JSON proposal 的 edits[].metadataapplyProposal() 构造 HarnessEntry "refine"

两者写入的是同一个字段------_upsert 不区分来源,只做 merge 或覆盖(harness.py:398-399):

python 复制代码
# 已有 entry:显式传了 metadata 就覆盖,None 则保留旧值
if metadata is not None:
    existing.metadata = dict(metadata)

# 新 entry:
metadata=dict(metadata or {})

/refine 侧的 merge 逻辑一样------edit.metadata ?? before?.metadata ?? {}refinement.ts:819),没传就沿用旧值。

有限的还是无限的

完全自由(无限的) ------dict[str, Any],没有 schema、没有校验、没有可穷举的 key 集合。

对比 reference 字段(仅 skill kind 用)有严格校验------_validate_python_skill_reference()harness.py:131-141)强制要求 type == "python" + import + callablemetadata 没有任何对应的校验函数。

三层证据:

1. 类型定义------完全开放harness.py:108):

python 复制代码
metadata: dict[str, Any] = field(default_factory=dict)

2. load() 只做类型兜底,不校验内容harness.py:253-254):

python 复制代码
if not isinstance(entry_data.get("metadata"), dict):
    entry_data["metadata"] = {}
# 只检查"是不是 dict",不检查里面有什么 key

3. /refine 的 system prompt 只是建议,不是约束refinement.ts:142):

When an edit is persisted, include metadata such as {"scope":"local"} or {"scope":"global"} when that helps future review understand the intended blast radius.

这是一个 suggestion------LLM 可以放任何 key。proposal 的 JSON schema 示例里 metadata 就是个空对象 "metadata": {}refinement.ts:163),完全留给 LLM 自由发挥。

4. 没有任何代码读取特定的 metadata key 做行为决策。

整个代码库里对 metadata 的操作只有三类:

  • passthroughobjectRecord(entry.metadata) ?? {}refinement.ts:344)------原样搬运
  • fallback mergeedit.metadata ?? before?.metadata ?? {}refinement.ts:819)------没传就沿用旧值
  • rollback copymetadata: edit.before.metadatarefinement.ts:866)------回滚时原样拷回

没有人做 entry.metadata["scope"]entry.metadata.get("kind") 这类读取。metadata 被存储、合并、拷贝,但从不被语义化解读

metadata 是一个只写不读的开放式附件 ------LLM(或 /refine)可以往里塞任意 key-value,系统存储它但不基于它做任何决策。它的定位是"给未来的人类审查或 LLM 回顾留的注释",不是"驱动 agent 行为的结构化字段"。

Q4:prime-agent 的 skill 是可执行的 Python 包,这句话怎么理解?是 prime-agent 不支持通用 skill、自己重新实现了 skill 规范的意思吗?

不是。 prime-agent 没有抛弃 pi 的 Markdown skill,也没有重新实现 skill 规范------它扩展Agent Skills 标准,在原生的 Markdown skill 之上增加了一种 superset:Python-backed skill。两种 skill 共存,通过同一个 SKILL.md 入口发现。

两种 skill kind 共存:

typescript 复制代码
export type SkillKind = "markdown" | "python";
Markdown skill Python-backed skill
入口 SKILL.md SKILL.md(相同)
额外要求 pyproject.toml + src/<import_name>/__init__.py
LLM 怎么用 SKILL.md 全文,按指示操作 在 REPL 里直接 await <import_name>(...) 调用
是可执行包? 不是------是指令文档 是------editable install 到 kernel venv

发现逻辑------SKILL.md 是统一入口,Python 是附加检测。如果只有 SKILL.md,没有 pyproject.toml,那就是 pi 原生的 Markdown skill------和 pi 完全兼容。如果目录里有 pyproject.toml + src/__init__.py,就升级为 Python-backed skill。

别和 Harness State 的 skill 混淆------文件系统 skill 和 harness state skill entry 是两个不同的概念:

文件系统 skill(skills.ts Harness state skill(harness.py
存哪 磁盘上的 SKILL.md + 可选 Python 包 harness_state.json 里的 entry
谁创建 用户手写或 skill-creator skill 生成 LLM 通过 rlm.harness.create_skill(...)/refine 动态创建
kind 限制 markdownpython 都行 必须是 python ------强制 reference.type == "python"
是真实包? Python skill 是真实安装在 kernel venv 的包 不是包------是"可复用 Python 调用的持久化描述"
能执行? Python skill 可以 await import_name(...) 自身不能执行------它描述的是一个已存在的 Python callable 的调用契约

所以"prime-agent 的 skill 是可执行的 Python 包"这个说法只对 Python-backed skill 成立------Markdown skill 仍然是指令文档。prime-agent 没有重新实现 skill 规范,而是在 Agent Skills 标准之上做了向后兼容的扩展

七、下一章预告

下一篇文章将拆解 prime-agent 的 RLM 运行时------rlm-runtime.ts(TypeScript 侧的子 agent spawn 桥接)+ prompts/rlm.ts(RLM system prompt 构造)+ context-tree.ts(prompt-as-a-variable)+ semantic-edges.ts(context 分段标记)+ side-question.ts(侧问机制)。RLM 让 agent 从单层循环变成递归的多 agent 树------rlm() spawn 子 agent、子 agent 再 spawn 孙 agent,每层有自己的 context 和 REPL。

相关推荐
武子康3 小时前
Codex 后台任务结束了,为什么还不能直接接着用?
人工智能·llm·agent
DeepAgent4 小时前
AI Agent 入门指南(05):Agent 循环——观察、思考、行动
面试·agent
VIP_CQCRE6 小时前
把 Coze 接入 Ace Data Cloud:用 OpenAI Responses 协议快速扩展你的 AI Agent 模型能力
ai·大模型·agent·coze·acedatacloud
是Dream呀6 小时前
Harness 工程:让 Agent 真正把任务做完
人工智能·分布式·缓存·agent
Web3&Basketball7 小时前
Agent外传审计实战:3类失准事故拦截脚本
python·大模型·agent·性能调优·推理优化
武子康7 小时前
vLLM 的 token 预算还有余量,为什么请求仍被抢占?
人工智能·llm·agent
染指11108 小时前
120.Agent-LangChain核心组件-中间件-摘要中间件(SummarizationMiddleware)
人工智能·langchain·agent·agents
不好听6138 小时前
当 Agent 有了手,就得给它戴手套:安全与沙箱怎么兜底——Sub-Agents
agent
不好听6138 小时前
父—子代理为什么要上下文隔离——Sub-Agents
agent