Skip to content

提案:工具调用边界 + edit 工具族(对齐 pi-agent 的 agent-loop) #72

Description

@miaobyte

提案:工具调用边界 + edit 工具族(对齐 pi-agent 的 agent-loop)

蓝本:reference/pi-agent/agent-loop.ts(earendil-works/pi,packages/agent/src/agent-loop.ts)。
本文只提出方案与分期,不含实现。

一句话

byteseek 缺的不是若干 edit 工具,而是工具调用边界这一层。pi 把工具边界放在宿主循环里
(prepare → execute → finalize);byteseek 的循环是每轮现造的 kvlang 代码,所以这一层应落到
KV 树里lib/tool(契约、清单、结果记录)+ lib/edit(第一个工具族)。

现状(byteseek 已实现的部分)

已有 位置 说明
主循环 lib/byteseek/main.kv mainbrain input → classify → ensure_category → recall → llm·callbyteseek·run
代码脑 lib/byteseek/llm.kv call LLM 生成 → kvlang·vetkvlang·layout → 返回入口
裸 LLM lib/byteseek/llm.kv raw 供元能力复用
提示词即 KV lib/byteseek/prompt.kv/byteseek/prompt/system 可寻址、可自改
记忆 lib/byteseek/memory.kv /byteseek/memory/* + cat/*;子串召回;classify 语言跟随
能力记忆 lib/byteseek/memgen.kv 每个 /lib/<name> 浓缩成 ·mem
工具 lib/byteseek/{shell,python}.kv 都只是 networld/proc·exec 的薄包
执行 main.kv runvthread·call 同 vid 动态调用,跑完回主循环
自检 tests/selftest.kv vet + session 执行 + shell/python 捕获

缺口(对照 pi 的六个机制)

pi 机制 位置 byteseek 现状
三段式工具调用 prepare → execute → finalize prepareToolCall 607 / executePreparedToolCall 677 / finalizeExecutedToolCall 720 shell·run 直接 exec,无参数校验、无前置闸门、无结果规范化
工具结果作为消息回灌 context createToolResultMessage 784,isError :结果只 println 到终端,模型下一轮看不见
截断即全批失败 231 行 stopReason === "length"failToolCallsFromTruncatedMessage max_tokens 截断被当成 vet 语法错
工具声明串行 418 行 executionMode === "sequential" :没有并发概念,但 edit 类必须显式串行
终止由工具决定 shouldTerminateToolBatch 589 :只有 exit 关键词
执行钩子 beforeToolCall / afterToolCall / getSteeringMessages / prepareNextTurn beforeToolCall 正是 G1 权限闸门的形状)

直接后果byteseek·run 在生成失败时只打印 跳过执行(生成失败) 就返回,模型无从得知自己错在
哪里;工具报错也停在终端。这是「agent 不能自我修复」的根因,edit 工具缺失只是其中一个症状。

方案总纲

不搬 pi 的双层 while(byteseek 的循环是生成物,不是宿主),只把 pi 的三条语义落到树里:

  1. 工具契约统一:每个工具都是 kv rwfunc,遵守 校验 → 干跑 → 执行 → 回读 → 记账 五步。
  2. 工具结果落树并回灌:结果写 /byteseek/tool/<vid>/<n>,下一轮由 mainbrain 注入 prompt——
    这是 pi toolResult 消息的 KV 化,复用现有 memory 注入通道。
  3. 错误与截断是一等公民llm·call 区分「被截断 / vet 失败 / 运行时报错 / 工具报错」,
    分类回灌触发有界 repair。

具体设计

1. lib/edit.kv —— 第一个工具族

工具 语义 关键约定
edit·read(path, from, lines) 读文本(带行号) 只接受 UTF-8;默认窗口 + 大小上限
edit·write(path, text) 覆盖写 仅新建或整文件替换
edit·replace(path, old, new, dry) 精确串替换(核心) old 命中数必须 恰为 1:0 → error: 未找到,>1 → error: 歧义
edit·multi(edits) 多处编辑 全部成功才落盘(事务)
edit·grep(pattern, path, glob) / edit·glob(pattern) 定位 避免整文读入,省 token

底层全部是现有能力:networld/fs·{size,read,write,exists} + xv·reinterpret[]uint8
[]char/utf8)+ string·{find,slice,len}不新增 rwir,唯一例外见 P2 的 fs·rename

rwfunc replace(path, old, new, dry) -> (n) {
    fs·read(path, 0, size) -> raw ; xv·reinterpret(raw, "[]char/utf8") -> text
    string·find 计数 old ;!= 1 → error(不落盘,p0/p1:不容许尽力而为)
    dry == 1 → 返回命中位置与 diff
    overwrite → 回读比对 → 不一致则 error
}

2. 工具契约(lib/tool.kv

工具实现只写「做什么」,tool·call 负责边界策略:

tool·call(name, args) -> (result, kind)
  prepare : 工具在 /byteseek/tools/<name> 有声明(签名 + 是否串行 + 是否需确认)
            参数按签名校验;不过 → kind = "arg"
  gate    : before_hook(权限/沙箱白名单)→ 不过 → kind = "denied"(对应 pi beforeToolCall)
  execute : 调 rwfunc;异常 → kind = "runtime"
  record  : /byteseek/tool/<vid>/<n> = {name, args, kind, result, ts}
  result  : 回一段**给模型看的文本**(成功=摘要,失败=可行动的说明)

3. 工具清单是树数据

现在 prompt.kv 手写「执行 shell 用 shell·run、python 用 python·run」两条。改成:
/byteseek/tools/<name> 声明签名与用途,llm·call 拼 system prompt 时从树里生成工具清单。
新增工具 = 往树里写一条,提示词自动跟上——与「提示词即 KV 数据」同源,也让 agent 能自造工具。

4. 结果回灌与有界 repair(补 A3 / #7 前半)

mainbrain 每轮除了 [类别][相关记忆],再注入上一轮的工具结果摘要:

[上轮工具结果]
/byteseek/tool/1234/0 = edit·replace /tmp/a.py → error: old 命中 3 处,请带更多上下文

触发条件:上轮 kind != "ok"。上限 N=3(与 #41 迭代 2 的有界 repair 合并),失败历史落树可复盘。

5. 错误分类

kind 触发 回灌话术
truncated 响应被 max_tokens 截断(pi 231 行) 「输出被截断,请重发完整参数/代码」
vet kvlang·vet 不过 原文诊断
runtime 执行期 NameError / 工具异常 错误原文 + PC
arg / denied 参数校验不过 / 闸门拦截 原因 + 正确的签名/边界

与既有设计的接合点

既有 接合方式
提示词即 KV 数据 工具清单同为树数据,自举注入
memory 注入通道 工具结果复用同一注入点,不新开通道
vthread·call / ‥pc 工具执行进度落在 /vthread/{vid}/‥pc,结果落在 /byteseek/tool/<vid>/<n>,与 #69stackmem 同一族坐标
#69 函数执行脑 本提案的工具就是 thinknext 展开到的叶子动作;先有叶子,树才有意义
#41 JIT-Agent 总纲 本提案 ≈ 迭代 1(四模块)与迭代 2(repair)之间缺的「工具层」,可直接并入其依赖序
#66 生成代码格式检查 参数校验与 vet 是同一类闸门,共用 kind 分类
G1 沙箱 / G2 密钥 before_hook 就是它们的落点

分期与验收

内容 验收
P0 lib/edit.kvread/write/replace:UTF-8 校验、唯一命中、dry-run、回读校验 2 处命中 → error 且文件未变;0 处 → error;1 处 → 成功且回读一致;tests/selftest.kv 增对应断言,make test 全绿
P1 lib/tool.kv 契约 + 结果落 /byteseek/tool/<vid>/<n> + system prompt 工具清单自举 kvspace list /byteseek/tools/ 可见工具;删掉 prompt.kv 里手写的两条后仍能跑通;每次调用有记录
P2 结果回灌 + 有界 repair + kind 分类(含 truncated) 造一个必失败的 edit,观察两轮内自动修复;截断用例回灌话术正确
P3 edit·multi 事务 + grep/glob + fs·rename(唯一新增 rwir,原子替换)+ usage 记账入树 事务中途失败文件不变;改名后无残留临时文件

非目标

  • 不把 pi 的 session/turn 双层 while 搬进 kvlang(那是 01-anything/codex 启发:agent 核心循环分 session/task/turn/thread 四层 #8 的议题,且 byteseek 的循环是生成物)。
  • 不做并行工具调用;edit 类一律串行(对齐 pi 的 executionMode,但用更简单的「全部串行」)。
  • 不新增 Rust rwir,唯一例外是 P3 的 fs·rename(现有 fs·write 是截断式覆盖,崩在中间会毁文件)。
  • 不用 shell·runsed/patch 代替(转义地狱、不可审计),也不做 python 版编辑器(逻辑跑到树外)。

开放问题

  1. 工具清单注入方式:拼进 system prompt(简单)还是让模型用 kvspace·list("/byteseek/tools/") 自查(省 token、但多一轮)?
  2. 结果回灌粒度:全量回灌 vs 只回摘要 + 路径(推荐后者,与 memory 的薄注入一致)。
  3. edit·replace 是否默认 dry-run(更安全但多一轮往返)?
  4. 工具执行记录是每 vid 一份(/byteseek/tool/<vid>/<n>)还是全局追加(/byteseek/tool/<n>)——前者随 vthread 生命周期,后者可跨会话复盘。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions