From 98b5061df063238000e837197b862568419d78b6 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Tue, 15 Sep 2026 09:44:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=B1=82=E9=87=8D?= =?UTF-8?q?=E6=9E=84=EF=BC=9A=E5=B7=A5=E5=85=B7=E6=B8=85=E5=8D=95=E5=85=A5?= =?UTF-8?q?=E6=A0=91=20+=20=E5=8F=97=E7=AE=A1=E8=B0=83=E7=94=A8=EF=BC=9Bpy?= =?UTF-8?q?thon/shell=20=E4=B8=8B=E6=B2=89=20stdlib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agent 工具能力下沉到 kvlang 的 stdlib/networld(shell / python / edit), byteseek 只留「策略与记账」这一层: - **删 lib/byteseek/{python,shell}.kv** —— 实现在 stdlib networld 里了 - **新增 lib/byteseek/tool.kv**(受管工具):prepare(参数校验)→ 闸门 (before_hook,policy/deny 命中即拒)→ 调 stdlib 工具 → 每次调用记账 到 /byteseek/toolcall/。只覆盖改写类(edit·write/replace/multi); 只读工具直接调 stdlib。返回码沿用 stdlib,另有 8=denied、9=arg。 - **新增 lib/byteseek/tools.kv**(工具清单即 KV 数据):工具声明写进 /byteseek/tools/,system prompt 的工具清单由 tools·manifest() 从树里生成, 不再在 prompt.kv 手写——新增工具 = 往树里写一条。 - main / llm / prompt / memory / memgen 配套调整。 Co-Authored-By: Claude Code --- doc/substrate.md | 51 ++++++++++++++++--- lib/byteseek/llm.kv | 39 +++++++++++++-- lib/byteseek/main.kv | 110 +++++++++++++++++++++++++++++++++++++---- lib/byteseek/memgen.kv | 29 +---------- lib/byteseek/memory.kv | 28 +++-------- lib/byteseek/prompt.kv | 7 +-- lib/byteseek/python.kv | 12 ----- lib/byteseek/shell.kv | 13 ----- lib/byteseek/tool.kv | 102 ++++++++++++++++++++++++++++++++++++++ lib/byteseek/tools.kv | 43 ++++++++++++++++ 10 files changed, 338 insertions(+), 96 deletions(-) delete mode 100644 lib/byteseek/python.kv delete mode 100644 lib/byteseek/shell.kv create mode 100644 lib/byteseek/tool.kv create mode 100644 lib/byteseek/tools.kv diff --git a/doc/substrate.md b/doc/substrate.md index b4f3658..b0a3021 100644 --- a/doc/substrate.md +++ b/doc/substrate.md @@ -17,9 +17,11 @@ KV 树**(kvspace,后端 redis/fs/shm/s3)里。LLM、shell、python、json lib/byteseek/main.kv main / mainbrain(REPL 循环)/ run(动态执行生成程序) lib/byteseek/llm.kv llm·call(userinput) -> entry:代码脑,LLM 生成 kv 程序并入库 lib/byteseek/prompt.kv 系统提示(lib prompt 顶层写 → byteseek/prompt·init,layout 期种入) -lib/byteseek/shell.kv shell·run(cmd) -> out:bash 子进程,捕获 stdout -lib/byteseek/python.kv python·run(code) -> out:python3 子进程,捕获 stdout +lib/byteseek/memory.kv 记忆:remember / recall / classify(类别名跟随消息语言)/ lang +lib/byteseek/memgen.kv 能力记忆:把 /lib/ 浓缩成 ·mem 条目 +lib/byteseek/tools.kv 工具清单:layout 期把工具声明种进 /byteseek/tools/,manifest() 渲染 lib/local/config.kv LLM 配置(lib local 顶层写 → local·init,layout 期种入;gitignored) +vendor: kvlang stdlib 工具能力全在 stdlib——networld/shell·run / networld/python·run / networld/* ``` ## 引导与运行 @@ -36,7 +38,7 @@ kvlang byteseek·main # 运行:驱动已入库的 funckey(进入 REP 在 layout 期执行一次,把配置、系统提示种进 kvspace(跑完即删 init 子树)。kvlang 语法速览 (`kvlangbrief`)由 **kvlang 自己的 stdlib**(`kvlang/stdlib/kvlangbrief.kv`,lib kvlang)在 runtime-rs 启动时种入 `/lib/kvlang/kvlangbrief`,不属 byteseek lib。rwfunc(`main`/`mainbrain`/ -`run`/`llm·call`/`shell·run`/`python·run`)留在 `/lib` 下持久。之后 `kvlang byteseek·main` +`run`/`llm·call`/`networld/shell·run`/`networld/python·run`)留在 `/lib` 下持久。之后 `kvlang byteseek·main` 直接驱动 funckey,不再 layout、不再重跑 init。`byteseek·main` 是 funckey 路径(去 `/lib` 前缀),不是文件路径。 @@ -96,7 +98,7 @@ rwfunc run(entry:[]char/utf32) -> () { `vthread·call(funckey)` 是 kvlang 的 native builtin(`runtime/src/builtin.c`):在**当前 vthread** (同 vid)按运行时 funckey 造一次动态 `OP_CALL`,跑到被调函数结束再回到本指令的 NextPc。与 -`vthread·run` 不同——不新开 vid、不 WATCH 挂起——被调程序里的 rwir(`println`/`shell·run`/…) +`vthread·run` 不同——不新开 vid、不 WATCH 挂起——被调程序里的 rwir(`println`/`networld/shell·run`/…) 由当前驱动就地派发。故一次 REPL 请求、生成程序的执行、其内工具调用全在同一进程同一 vid 内完成, 契合「一个进程 ⟺ 一条 vthread」。 @@ -114,14 +116,51 @@ byteseek 不再注册任何自有 Rust rwir。所需能力全部是 kvlang 标 | `vthread·call(funckey)` | native builtin(同 vid 动态调用) | | `string·* / kv·* / xv·*` | native builtin | -`shell·run` / `python·run` 是 `lib/` 下的 kv rwfunc:把命令包成 `{"bash","-c",cmd}` / +`networld/shell·run` / `networld/python·run` 是 `lib/` 下的 kv rwfunc:把命令包成 `{"bash","-c",cmd}` / `{"python3","-c",code}` 交给 `networld/proc·exec`,绑定 stdout/stderr 写槽即捕获(`@[]uint8` 扩展句柄,`println` 读时按 body 前缀 `/networld/{host}/proc` 路由回兑现物理字节),返回 stdout。 +## 文件编辑:networld/edit(kvlang stdlib) + +`networld/edit` 是 kvlang stdlib(`kvlang/stdlib/networld/edit.kv`,tutorial +`14-networld/12-edit.kv`)里 `networld/fs`(字节原语 + 只读检索)之上的**改写语义层**: + +| 工具 | 语义 | +|------|------| +| `networld/edit·read(p, from, lines)` | 行号窗口视图(给模型看,不是全文 dump),返回 (视图, 总行数, 字节数) | +| `networld/edit·write(p, t)` | UTF-8 覆盖写 + 回读校验 | +| `networld/edit·replace(p, old, new, dry)` | 单处精确替换:`old` 必须**恰命中 1 处**,否则一个字节都不动 | +| `networld/edit·multi(p, olds, news, dry)` | 事务式多处替换:全部成功才落盘 | + +返回码统一:`0` 成功 / `1` 未找到 / `2` 歧义(命中多处)/ `3` 不可读 / `4` 写失败 / `5` 回读不一致 / +`6` 两数组长度不等。库不产出话术——「怎么说给模型听」属 harness 策略。 + +## 工具调用边界(issue #72) + +pi 的 toolResult 边界在 byteseek 里落到 KV 树上: + +| 环节 | 落点 | 说明 | +|------|------|------| +| 工具清单 | `/byteseek/tools/*` + `byteseek/tools·manifest()` | 树数据;`llm·call` 拼 system prompt 时注入《可用工具》段,新增工具 = 写一条 | +| 执行 | `byteseek·run(entry) -> (kind, detail)` | `vthread·create` + `vthread·run` 跑在**子 vid**,父(会话)不死 | +| 记账 | `/byteseek/tool/ = "kind \| detail \| entry"` | 可寻址、可复盘;detail 截断到 300 字 | +| 用量 | `/byteseek/usage/` + `usage/last` = `prompt=… completion=… total=…` | 每次 LLM 调用记一次(`llm·usage()`) | +| 程序自报 | `/byteseek/last/result` = `ok` / `fail: 原因` | 工具级失败(如 `edit·replace` code != 0)由程序按协议上报 | +| 回灌 | `byteseek·solve(prompt)` | 失败把 `[上轮执行未成功] kind:detail` 拼回 prompt,有界重试 ≤3 轮 | + +kind 分类:`truncated`(被 max_tokens 截断)/ `vet`(生成物不过闸)/ `runtime`(子 vthread 报错, +读 `/vthread//‥error/msg`)/ `fail`(程序自报失败)/ `ok` / `empty`。 + +配套 runtime 语义:`vthread·run(vid)` 下**子 vthread 自己失败不再冒泡杀死父**——监督者活着读 +`/vthread//‥status` 与 `‥error/msg`(一个子任务失败不该结束整条会话)。 + +落盘是原子的:`networld/edit·write/replace/multi` 都走 `networld/edit·commit` —— 写 `

.tmp` +→ `networld/fs·rename` 覆盖 p → 回读校验;失败时 p 保持原样,不留半截文件。 + ## 已验证(0rs) - `KVLANG_LIB=lib kvlang` 引导:config/语法速览/系统提示三 init 于 layout 期种入,rwfunc 持久。 - `kvlang byteseek·main`:REPL 循环、`exit` 退出、无 key 时 `llm·call` 回填 error 且 `byteseek·run` 跳过。 - `byteseek·run(entry)` → `vthread·call`:同 vid 动态执行入库的 session 程序,其内 rwir 就地派发。 -- `shell·run` / `python·run`:经 `networld/proc·exec` 捕获子进程 stdout 并透明兑现。 +- `networld/shell·run` / `networld/python·run`:经 `networld/proc·exec` 捕获子进程 stdout 并透明兑现。 - `make test`(无网络无 LLM,shm 后端):vet + session 执行 + shell/python 捕获全链路通过。 diff --git a/lib/byteseek/llm.kv b/lib/byteseek/llm.kv index f4bbf93..4393767 100644 --- a/lib/byteseek/llm.kv +++ b/lib/byteseek/llm.kv @@ -4,7 +4,9 @@ lib llm { rwfunc call(userinput:[]char/utf32) -> (entry:[]char/utf32) { - kvspace·get("/byteseek/llm.key") -> key + key = "" + kvspace·has("/byteseek/llm.key") -> haskey + if (haskey) { kvspace·get("/byteseek/llm.key") -> key } if (key == "") { "error: 未设置 DEEPSEEK_API_KEY" -> entry return @@ -14,7 +16,8 @@ lib llm { if (print != "0") { println("[llm] 需求:", userinput) } kvspace·get("/byteseek/prompt/system") -> sys kvspace·get("/lib/kvlang/kvlangbrief") -> brief - sys + "\n\n=== kvlang 语法速览 ===\n" + brief -> sys + byteseek/tools·manifest() -> tools + sys + "\n\n=== 可用工具(/byteseek/tools/ 树数据)===\n" + tools + "\n\n=== kvlang 语法速览 ===\n" + brief -> sys "请把下面的需求翻译成一段 kvlang 程序。严格按系统提示的格式输出,只输出 / 两段,不要解释:\n\n" + userinput -> user sys -> /tmp/esc/sys json·to(/tmp/esc/sys) -> sys_j @@ -29,7 +32,13 @@ lib llm { return } json·from(resp) -> /tmp/llmresp + llm·usage() kvspace·get("/tmp/llmresp·choices·[0]·message·content") -> content + kvspace·get("/tmp/llmresp·choices·[0]·finish_reason") -> finish + if (finish == "length") { + "truncated: 输出被 max_tokens 截断,请重发完整的 /" -> entry + return + } if (content == "") { "error: LLM 响应无内容: " + resp -> entry return @@ -74,7 +83,9 @@ lib llm { // llm·raw(sys, user) -> text:裸 LLM 调用,返回 assistant 文本(不生成/不执行 kv 代码)。 // 供 memgen 等需要「LLM 直接产出一段文本」的元能力复用。 rwfunc raw(sys:[]char/utf32, user:[]char/utf32) -> (text:[]char/utf32) { - kvspace·get("/byteseek/llm.key") -> key + key = "" + kvspace·has("/byteseek/llm.key") -> haskey + if (haskey) { kvspace·get("/byteseek/llm.key") -> key } if (key == "") { "error: 未设置 DEEPSEEK_API_KEY" -> text return @@ -93,11 +104,33 @@ lib llm { return } json·from(resp) -> /tmp/llmresp + llm·usage() kvspace·get("/tmp/llmresp·choices·[0]·message·content") -> content + kvspace·get("/tmp/llmresp·choices·[0]·finish_reason") -> finish + if (finish == "length") { + "error: 输出被 max_tokens 截断(截断的回应不可用,请缩短需求或提高上限)" -> text + return + } if (content == "") { "error: LLM 响应无内容: " + resp -> text return } content -> text } + + // llm·usage():把本次响应的 token 用量记进 /byteseek/usage/(记账入树,可复盘)。 + rwfunc usage() -> () { + kvspace·listlen("/byteseek/usage/") -> n + string·formatint(n, 10) -> no + kvspace·get("/tmp/llmresp·usage·prompt_tokens") -> pt + kvspace·get("/tmp/llmresp·usage·completion_tokens") -> ct + kvspace·get("/tmp/llmresp·usage·total_tokens") -> tt + string·set(pt) -> pts + string·set(ct) -> cts + string·set(tt) -> tts + "prompt=" + pts + " completion=" + cts + " total=" + tts -> rec + "/byteseek/usage/" + no -> path + kvspace·set(path, rec) + kvspace·set("/byteseek/usage/last", rec) + } } diff --git a/lib/byteseek/main.kv b/lib/byteseek/main.kv index 19355a7..6affc96 100644 --- a/lib/byteseek/main.kv +++ b/lib/byteseek/main.kv @@ -24,21 +24,111 @@ lib byteseek { } else { userinput + "\n\n[类别] " + cat + "\n\n[相关记忆]\n" + mem -> prompt } - llm·call(prompt) -> entry - byteseek·run(entry) + kvspace·has("/byteseek/last/kind") -> hask + if (hask) { + kvspace·get("/byteseek/last/kind") -> lk + if (lk != "ok") { + kvspace·get("/byteseek/last/detail") -> ld + prompt + "\n\n[上轮执行未成功] " + lk + ":" + ld -> prompt + } + } + byteseek·solve(prompt) } } println("bye.") } - // byteseek·run(entry):在当前 vthread(同 vid)动态调用生成程序的入口 funckey,跑完回主循环。 - rwfunc run(entry:[]char/utf32) -> () { - if (entry == "") { return } - string·find(entry, "error") -> iserr - if (iserr == 0) { - println("[byteseek] 跳过执行(生成失败):", entry) - return + // byteseek·solve(prompt):生成 → 执行 → 失败回灌 → 有界重试(≤3 轮)。 + // 失败回灌是 pi agent-loop 的 toolResult(isError) 的 KV 化:模型下一轮看得见自己错在哪。 + rwfunc solve(prompt:[]char/utf32) -> () { + llm·call(prompt) -> entry + byteseek·run(entry) -> (kind, detail) + attempt = 1 + while (kind != "ok" && attempt <= 3) { + byteseek·brief(detail) -> bd + prompt + "\n\n[上轮执行未成功] " + kind + ":" + bd + "\n请修正后重新生成(只输出 /)。" -> retry + llm·call(retry) -> entry + byteseek·run(entry) -> (kind, detail) + attempt + 1 -> attempt + } + if (kind != "ok") { + println("[byteseek] 重试 3 轮仍未成功:", kind, "——", detail) + } + } + + // byteseek·run(entry) -> (kind, detail):在当前 vthread(同 vid)执行生成程序并记账。 + // kind empty / truncated / vet / fail / ok + // 工具级失败由程序按协议写 /byteseek/last/result("fail: 原因");没写 = ok。 + rwfunc run(entry:[]char/utf32) -> (kind:[]char/utf32, detail:[]char/utf32) { + "" -> kind + "" -> detail + if (entry == "") { + "empty" -> kind + } else { + string·find(entry, "truncated") -> t + string·find(entry, "error") -> e + if (t == 0) { + "truncated" -> kind + entry -> detail + } else { + if (e == 0) { + "vet" -> kind + entry -> detail + } else { + kvspace·set("/byteseek/last/result", "") + vthread·create(entry) -> vid + char/utf32(vid) -> vid + if (vid == "") { + "runtime" -> kind + "入口不存在: " + entry -> detail + } else { + vthread·run(vid) + "/vthread/" + vid + "/‥status" -> sp + kvspace·get(sp) -> st + if (st == "error") { + "runtime" -> kind + "/vthread/" + vid + "/‥error/msg" -> ep + kvspace·get(ep) -> em + char/utf32(em) -> em + em -> detail + } else { + kvspace·get("/byteseek/last/result") -> rep + if (rep == "" || rep == "ok") { + "ok" -> kind + } else { + "fail" -> kind + rep -> detail + } + } + } + } + } + } + byteseek·record(kind, detail, entry) + } + + // byteseek·record(kind, detail, entry):一次执行的记录落 /byteseek/tool/(可寻址、可复盘)。 + rwfunc record(kind:[]char/utf32, detail:[]char/utf32, entry:[]char/utf32) -> () { + kvspace·listlen("/byteseek/tool/") -> n + string·formatint(n, 10) -> no + "/byteseek/tool/" + no -> path + byteseek·brief(detail) -> bd + kind + " | " + bd + " | " + entry -> rec + kvspace·set(path, rec) + kvspace·set("/byteseek/last/kind", kind) + kvspace·set("/byteseek/last/detail", bd) + if (kind != "ok") { + println("[byteseek] 执行未成功:", kind, "——", bd) + } + } + + // brief(detail) -> s:详情截断到 300 字(整页 HTML 之类的错误体不该灌进 prompt 与记录)。 + rwfunc brief(detail:[]char/utf32) -> (s:[]char/utf32) { + string·len(detail) -> n + if (n > 300) { + string·slice(detail, 0, 300) -> s + } else { + detail -> s } - vthread·call(entry) } } diff --git a/lib/byteseek/memgen.kv b/lib/byteseek/memgen.kv index e127e5d..e3992a1 100644 --- a/lib/byteseek/memgen.kv +++ b/lib/byteseek/memgen.kv @@ -1,39 +1,14 @@ // byteseek 能力记忆生成器(全 kvlang):把 /lib 下各目录的能力浓缩成一条 ·mem 记忆 key。 // 后缀约定:目录 /lib/ 的能力记忆存在 key /lib/.mem。 -// fnlist(name) —— 枚举 /lib/· 下的函数/常量名(.src 条目去后缀,目录条目跳过)。 +// (能力名枚举用 kvlang stdlib 的 kvlang/reflect·fnlist) // summarize(name) —— 若 /lib/·mem 不存在,用 LLM 生成中文能力记忆并写入该 key。 // build() —— 对 kvlang stdlib 的目录递归概括一次(幂等:已有 ·mem 即跳过)。 lib byteseek { lib memgen { - rwfunc fnlist(name:[]char/utf32) -> (listing:[]char/utf32) { - "" -> listing - "/lib/" + name + "·" -> mdir - kvspace·listlen(mdir) -> n - i = 0 - while (i < n) { - kvspace·listn(mdir, i) -> entry - char/utf32(entry) -> entry - string·len(entry) -> elen - string·find(entry, ".src") -> p - if (p >= 0) { - elen - 4 -> cut - string·slice(entry, 0, cut) -> fn - if (listing == "") { fn -> listing } else { listing + ", " + fn -> listing } - } else { - elen - 1 -> last - string·char(entry, last) -> tail - if (tail != "/") { - if (listing == "") { entry -> listing } else { listing + ", " + entry -> listing } - } - } - i + 1 -> i - } - } - rwfunc summarize(name:[]char/utf32) -> () { kvspace·has("/lib/" + name + ".mem") -> exists if (exists == true) { return } - fnlist(name) -> listing + kvlang/reflect·fnlist(name) -> listing "你是 kvlang 能力记忆生成器。kvspace 目录 /lib/" + name + " 提供以下能力:" + listing + "。请用中文写一段不超过 200 字的紧凑能力记忆:用 · 分隔,每个能力名后跟一句话用途。只输出记忆文本本身,不要代码、不要解释、不要 markdown。" -> user llm·raw("你是 kvlang 能力记忆生成器。只输出简洁的中文记忆文本。", user) -> text string·find(text, "error") -> iserr diff --git a/lib/byteseek/memory.kv b/lib/byteseek/memory.kv index 77c046b..2440255 100644 --- a/lib/byteseek/memory.kv +++ b/lib/byteseek/memory.kv @@ -3,7 +3,7 @@ // recall(query) —— 记忆检查:扫描 /byteseek/memory/ 子树,自动滤出与 query 相关的关键树路径, // 返回多行 "key = value",供 mainbrain 注入 LLM 上下文。 // classify(msg) —— 用 LLM 把消息归入一个简短类别;类别名与消息语言相同(lang 判定,默认中文)。 -// lang(msg) —— 判消息文字类型(假名 / 谚文 / 汉字 / 拉丁),默认中文。 +// lang(msg) —— 语言显示名:string·lang 的 BCP-47 短码(zh/ja/ko/en)映射,默认中文。 // ensure_category(cat) —— 若类别不存在,则在 /byteseek/memory/cat/ 新建。 // 匹配规则:query 与 key 互相包含,或 query 与 value 互相包含(子串匹配,大小写敏感)。 lib byteseek { @@ -46,29 +46,13 @@ lib byteseek { } } - // lang(msg) —— 判消息的文字类型:假名 > 谚文 > 汉字 > 拉丁,都不命中则默认中文。 + // lang(msg) —— 语言显示名:由 string·lang 的语言子标签映射,都不命中默认中文。 rwfunc lang(msg:[]char/utf32) -> (lg:[]char/utf32) { - han = 0 - kana = 0 - hangul = 0 - latin = 0 - string·len(msg) -> n - i = 0 - while (i < n) { - string·char(msg, i) -> ch - string·ord(ch) -> c - if (c >= 12352) { if (c <= 12543) { kana = 1 } } // 平/片假名 - if (c >= 19968) { if (c <= 40959) { han = 1 } } // 汉字 - if (c >= 44032) { if (c <= 55215) { hangul = 1 } } // 谚文 - if (c >= 65) { if (c <= 90) { latin = 1 } } // A-Z - if (c >= 97) { if (c <= 122) { latin = 1 } } // a-z - i + 1 -> i - } + string·lang(msg) -> code "中文" -> lg - if (latin == 1) { "English" -> lg } - if (han == 1) { "中文" -> lg } - if (hangul == 1) { "한국어" -> lg } - if (kana == 1) { "日本語" -> lg } + if (code == "en") { "English" -> lg } + if (code == "ja") { "日本語" -> lg } + if (code == "ko") { "한국어" -> lg } } rwfunc fallback_cat(lg:[]char/utf32) -> (cat:[]char/utf32) { diff --git a/lib/byteseek/prompt.kv b/lib/byteseek/prompt.kv index 4b6f16e..595febe 100644 --- a/lib/byteseek/prompt.kv +++ b/lib/byteseek/prompt.kv @@ -26,15 +26,16 @@ rwfunc main() -> () { show_date rwfunc main() -> () { - shell·run("date") -> out + networld/shell·run("date") -> out println(out) } 硬规则: - 只写顶层 rwfunc main 定义,不要写 main() 调用。禁止 lib 块,禁止把代码包进别的结构,禁止 markdown 围栏。 -- 执行 shell 命令:shell·run(命令字符串) -> 输出变量。 -- 执行 python:python·run(代码字符串) -> 输出变量。 +- 工具清单见 system prompt 的《可用工具》段——那一段由 /byteseek/tools/ 树生成,只列真实存在的工具;不要调用清单以外的名字。 +- 改文件必须走清单里的 byteseek/tool·edit_*(闸门 + 记账;replace 只在原文唯一命中时落盘);不要用 string·replace + networld/fs·writetext,也不要直调 networld/edit·* 绕过闸门。 +- 若你判断任务没完成(例如工具返回非 0 的 code),把原因写进 `/byteseek/last/result`(格式 `fail: 原因`);byteseek 会把它回灌给你重试。成功不用写。 - 最终结果用 println(...) 打印。 - 如果需求后面附有 `[相关记忆]`(若干 `key = value` 行),这些是已沉淀在 kvspace 树里的记忆;优先利用其中与任务相关的事实或结论来生成代码。 - 严格遵循下方《kvlang 语法速览》:乘用 ×、除用 ÷;数组用 [] 前缀;类型定宽(int64/float64,没有 int/float);写参累加前先显式置 0。"# -> /byteseek/prompt/system diff --git a/lib/byteseek/python.kv b/lib/byteseek/python.kv deleted file mode 100644 index 4b1738b..0000000 --- a/lib/byteseek/python.kv +++ /dev/null @@ -1,12 +0,0 @@ -// python·run(code) -> out:跑一段 python3 -c,返回 stdout(@ 句柄,读时兑现)。 - -lib python { - rwfunc run(code:[]char/utf32) -> (out:[]char/utf32) { - println("🔧 python:", code) - noenv:[int64]·[]char/utf32 = {} - a:[int64]·[]char/utf32 = {"python3", "-c", code} - networld/proc·exec(a, noenv) -> ret, o, e - println("↳", o, e) - o -> out - } -} diff --git a/lib/byteseek/shell.kv b/lib/byteseek/shell.kv deleted file mode 100644 index 249dc49..0000000 --- a/lib/byteseek/shell.kv +++ /dev/null @@ -1,13 +0,0 @@ -// shell·run(cmd) -> out:跑一段 bash,返回 stdout(@ 句柄,读时兑现)。 -// 绑定 out/err 写槽 → networld/proc·exec 管道捕获两流;就地打印工具头与两流后返回 stdout。 - -lib shell { - rwfunc run(cmd:[]char/utf32) -> (out:[]char/utf32) { - println("🔧 shell:", cmd) - noenv:[int64]·[]char/utf32 = {} - a:[int64]·[]char/utf32 = {"bash", "-c", cmd} - networld/proc·exec(a, noenv) -> code, o, e - println("↳", o, e) - o -> out - } -} diff --git a/lib/byteseek/tool.kv b/lib/byteseek/tool.kv new file mode 100644 index 0000000..4cf1190 --- /dev/null +++ b/lib/byteseek/tool.kv @@ -0,0 +1,102 @@ +// byteseek 受管工具:prepare(参数校验)→ 闸门(before_hook)→ 调 stdlib 工具 → 每次调用记账。 +// /byteseek/policy/deny —— 换行分隔的路径前缀,命中即拒绝(对应 pi 的 beforeToolCall) +// /byteseek/toolcall/ —— 每次调用的记录 "name | target | 结果" +// 只覆盖改写类(edit·write/replace/multi);只读工具(read/grep/glob/fs·readtext)直接调 stdlib。 +// 返回码沿用 stdlib,另有 8 = 被策略拒绝(denied)、9 = 参数缺失(arg,如漏传 dry)。 + +lib byteseek { + lib tool { + // prescreen(p, dry) -> code:prepare 期校验 —— 0 通过 / 9 参数缺失 / 8 被策略拒绝。 + // 缺参在 kvlang 里读出来是字符串 "None",这里显式挡住:宁可不做,也不要按默认值误落盘。 + rwfunc prescreen(p:[]char/utf32, dry:int64) -> (code:int64) { + string·set(p) -> ps + string·set(dry) -> ds + if (string·cmp(ps, "None") == 0 || string·len(ps) == 0 || string·cmp(ds, "None") == 0) { + code = 9 + } else { + byteseek/tool·denied(p) -> dn + if (dn) { code = 8 } else { code = 0 } + } + } + + // denied(p) -> b:p 是否命中 /byteseek/policy/deny 里的路径前缀(无策略 = 不拒)。 + rwfunc denied(p:[]char/utf32) -> (b:bool) { + b = false + kvspace·has("/byteseek/policy/deny") -> has + if (has) { + kvspace·get("/byteseek/policy/deny") -> raw + char/utf32(raw) -> list + parts:[int64]·[]char/utf32 = {} + string·lines(list) -> (parts, n) + string·len(p) -> plen + i = 0 + while (i < n) { + kvspace·listn(parts, i) -> k + kvspace·get(parts, k) -> pre + char/utf32(pre) -> pre + string·len(pre) -> l + if (l > 0 && l <= plen) { + string·slice(p, 0, l) -> head + if (string·cmp(head, pre) == 0) { b = true } + } + i + 1 -> i + } + } + } + + // callrec(name, target, res):一次受管调用的记录落 /byteseek/toolcall/。 + rwfunc callrec(name:[]char/utf32, target:[]char/utf32, res:[]char/utf32) -> () { + kvspace·listlen("/byteseek/toolcall/") -> n + string·formatint(n, 10) -> no + name + " | " + target + " | " + res -> rec + "/byteseek/toolcall/" + no -> path + kvspace·set(path, rec) + } + + rwfunc edit_write(p:[]char/utf32, t:[]char/utf32) -> (n:int64, code:int64) { + byteseek/tool·prescreen(p, 0) -> pc + string·set(t) -> ts + if (pc == 0 && string·cmp(ts, "None") == 0) { pc = 9 } + if (pc != 0) { + n = -1 + code = pc + byteseek/tool·callrec("edit_write", p, "reject") + } else { + networld/edit·write(p, t) -> (n, code) + string·formatint(code, 10) -> cs + byteseek/tool·callrec("edit_write", p, cs) + } + } + + rwfunc edit_replace(p:[]char/utf32, old:[]char/utf32, new:[]char/utf32, + dry:int64) -> (done:int64, hit:int64, code:int64) { + byteseek/tool·prescreen(p, dry) -> pc + if (pc != 0) { + done = 0 + hit = 0 + code = pc + byteseek/tool·callrec("edit_replace", p, "reject") + } else { + networld/edit·replace(p, old, new, dry) -> (done, hit, code) + string·formatint(code, 10) -> cs + byteseek/tool·callrec("edit_replace", p, cs) + } + } + + rwfunc edit_multi(p:[]char/utf32, olds:*[int64]·[]char/utf32, + news:*[int64]·[]char/utf32, dry:int64) -> ( + done:int64, bad:int64, code:int64) { + byteseek/tool·prescreen(p, dry) -> pc + if (pc != 0) { + done = 0 + bad = -1 + code = pc + byteseek/tool·callrec("edit_multi", p, "reject") + } else { + networld/edit·multi(p, olds, news, dry) -> (done, bad, code) + string·formatint(code, 10) -> cs + byteseek/tool·callrec("edit_multi", p, cs) + } + } + } +} diff --git a/lib/byteseek/tools.kv b/lib/byteseek/tools.kv new file mode 100644 index 0000000..fe9561d --- /dev/null +++ b/lib/byteseek/tools.kv @@ -0,0 +1,43 @@ +// byteseek 工具清单:工具声明也是 KV 数据——新增工具 = 往 /byteseek/tools/ 写一条, +// system prompt 里的工具清单由 tools·manifest() 从树里生成(不再在 prompt.kv 里手写)。 +// key 用 . 连接(. 在 kvlang 里是普通字符,成员分隔符是 ·),value 是「调用签名 —— 用途」一行。 + +lib byteseek { + lib tools { + kvspace·deltree("/byteseek/tools") + kvspace·set("/byteseek/tools/networld.shell.run", "networld/shell·run(cmd) -> out —— 跑 bash -c,返回 stdout") + kvspace·set("/byteseek/tools/networld.python.run", "networld/python·run(code) -> out —— 跑 python3 -c,返回 stdout") + kvspace·set("/byteseek/tools/networld.edit.read", "networld/edit·read(path, from, lines) -> (view, total, sz) —— 行号窗口视图;lines 传 0 = 到末尾") + kvspace·set("/byteseek/tools/byteseek.tool.edit_write", "byteseek/tool·edit_write(path, text) -> (n, code) —— 原子覆盖写(受闸门与记账;code 8 = 被策略拒绝)") + kvspace·set("/byteseek/tools/byteseek.tool.edit_replace", "byteseek/tool·edit_replace(path, old, new, dry) -> (done, hit, code) —— 精确替换:old 必须唯一命中,否则一个字节都不动;四个参数都要给:dry=0 落盘 / dry=1 只预演;受闸门与记账") + kvspace·set("/byteseek/tools/byteseek.tool.edit_multi", "byteseek/tool·edit_multi(path, olds, news, dry) -> (done, bad, code) —— 事务式多处替换,全成功才落盘;四个参数都要给:dry=0 落盘 / dry=1 只预演;受闸门与记账") + kvspace·set("/byteseek/tools/networld.fs.readtext", "networld/fs·readtext(path) -> (text, size) —— 读整个文件为文本") + kvspace·set("/byteseek/tools/networld.fs.writetext", "networld/fs·writetext(path, text) -> n —— 把文本写入文件") + kvspace·set("/byteseek/tools/networld.fs.glob", "networld/fs·glob(pattern) -> paths —— 按通配找文件(\\n 连接)") + kvspace·set("/byteseek/tools/networld.fs.grep", "networld/fs·grep(pattern, path, glob) -> hits —— 逐行查找,输出 \\\"路径:行号:文本\\\"") + kvspace·set("/byteseek/tools/http.get", "http·get(url) -> resp —— GET 请求") + kvspace·set("/byteseek/tools/http.post", "http·post(url, body) -> resp —— POST 请求") + kvspace·set("/byteseek/tools/string.len", "string·len(s) -> n —— 字符数") + kvspace·set("/byteseek/tools/kvspace.get", "kvspace·get(path) -> v —— 读 KV 树") + kvspace·set("/byteseek/tools/kvspace.set", "kvspace·set(path, v) —— 写 KV 树") + + // manifest() -> text:把工具清单渲染成 "- 签名 —— 用途" 多行文本(名字序)。 + rwfunc manifest() -> (text:[]char/utf32) { + "" -> text + kvspace·list("/byteseek/tools/") -> names + kvspace·listlen(names) -> n + i = 0 + while (i < n) { + kvspace·listn(names, i) -> k + kvspace·get(names, k) -> v + char/utf32(v) -> v + if (i == 0) { + text + "- " + v -> text + } else { + text + "\n- " + v -> text + } + i + 1 -> i + } + } + } +} From 46ba42599035afef00c5c287792c8d6713ff5f4e Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Tue, 15 Sep 2026 09:44:28 +0800 Subject: [PATCH 2/2] =?UTF-8?q?selftest:=20=E6=89=A9=E5=85=85=E6=96=AD?= =?UTF-8?q?=E8=A8=80=EF=BC=9B=E6=96=B0=E5=A2=9E=20repl=20=E5=86=92?= =?UTF-8?q?=E7=83=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tests/selftest.kv:覆盖 tool 层新语义(RUN/FAILKIND/TRUNC/RUNTIME/ TOOLS/REC/DENIED=8/ALLOWED=0/ARG=9/TOOLCALL/EDIT) - Makefile:test 目标同步这些断言;另加一步 **repl 冒烟**—— 管道喂 hi/exit 跑 byteseek·main,要求出现 "bye." 且全程无 TypeError Co-Authored-By: Claude Code --- Makefile | 18 ++++++++++++++++- tests/selftest.kv | 49 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 29715eb..36faac1 100644 --- a/Makefile +++ b/Makefile @@ -24,4 +24,20 @@ test: boot echo "$$out" | grep -q "vet(good)= ok" && \ echo "$$out" | grep -q "SESSION: selftest-shell" && \ echo "$$out" | grep -q "PY: 42" && \ - echo "$$out" | grep -q "MEM: theme" && echo "✅ selftest 通过" || { echo "❌ selftest 失败"; exit 1; } + echo "$$out" | grep -q "RUN: ok" && \ + echo "$$out" | grep -q "FAILKIND: fail" && \ + echo "$$out" | grep -q "TRUNC: truncated" && \ + echo "$$out" | grep -q "RUNTIME: runtime" && \ + echo "$$out" | grep -q "TOOLS: true" && \ + echo "$$out" | grep -q "REC: true" && \ + echo "$$out" | grep -q "DENIED: 8" && \ + echo "$$out" | grep -q "ALLOWED: 0" && \ + echo "$$out" | grep -q "ARG: 9" && \ + echo "$$out" | grep -q "TOOLCALL: true" && \ + echo "$$out" | grep -q "MEM: theme" && \ + echo "$$out" | grep -q "EDIT: 1 0 a c" && echo "✅ selftest 通过" || { echo "❌ selftest 失败"; exit 1; } + @dsn=$${REPL_SMOKE_DSN:-shm:///tmp/byteseek_repl_smoke}; \ + KVLANG_LIB=lib KVSPACE=$$dsn kvlang >/dev/null 2>&1; \ + KVSPACE=$$dsn kvlang -c 'kvspace·del("/byteseek/llm.key") -> _' >/dev/null 2>&1; \ + out=$$(printf 'hi\nexit\n' | KVSPACE=$$dsn timeout 120 kvlang byteseek·main 2>&1); \ + echo "$$out" | grep -q "bye." && ! echo "$$out" | grep -q "TypeError" && echo "✅ repl 冒烟通过" || { echo "❌ repl 冒烟失败"; echo "$$out" | tail -5; exit 1; } diff --git a/tests/selftest.kv b/tests/selftest.kv index 72451b2..a89988c 100644 --- a/tests/selftest.kv +++ b/tests/selftest.kv @@ -1,6 +1,6 @@ // byteseek 自检(无网络、无 LLM):验证全 kvlang 主脑的执行链—— // byteseek·run(entry) → vthread·call 在当前 vthread 动态执行生成程序; -// shell·run / python·run 经 networld/proc·exec 捕获子进程 stdout。 +// networld/shell·run / networld/python·run 经 networld/proc·exec 捕获子进程 stdout。 // 先 boot byteseek 库(KVLANG_LIB=lib kvlang),再 layout 本文件,最后 run selftest·go。 // 期望输出: // vet(good)= ok @@ -12,24 +12,63 @@ lib byteseek { lib session { lib demo { rwfunc main() -> () { - shell·run("echo selftest-shell") -> o + networld/shell·run("echo selftest-shell") -> o println("SESSION:", o) - python·run("print(6*7)") -> p + networld/python·run("print(6*7)") -> p println("PY:", p) } byteseek/session/demo·main() } + lib bad { + rwfunc main() -> () { + kvspace·set("/byteseek/last/result", "fail: 故意失败") + } + byteseek/session/bad·main() + } + lib boom { + rwfunc main() -> () { + no_such_tool·call() -> x + } + byteseek/session/boom·main() + } } } lib selftest { rwfunc go() -> () { - kvlanglayout·vet("rwfunc twice(a:int64) -> (b:int64) { a + a -> b }") -> v + kvlang·vet("rwfunc twice(a:int64) -> (b:int64) { a + a -> b }") -> v println("vet(good)=", v) - byteseek·run("byteseek/session/demo·init") + byteseek·run("byteseek/session/demo·init") -> (k0, d0) + println("RUN:", k0) + byteseek·run("byteseek/session/bad·init") -> (k1, d1) + println("FAILKIND:", k1) + byteseek·run("truncated: 试") -> (k2, d2) + println("TRUNC:", k2) + byteseek·run("byteseek/session/boom·init") -> (k3, d3) + println("RUNTIME:", k3) + byteseek/tools·manifest() -> mf + string·len(mf) -> mlen + println("TOOLS:", mlen > 0) + kvspace·listlen("/byteseek/tool/") -> recn + println("REC:", recn > 0) + kvspace·set("/byteseek/policy/deny", "/etc\n/usr\n") + byteseek/tool·edit_replace("/etc/hosts", "a", "b", 0) -> (td, th, tc) + println("DENIED:", tc) + kvspace·set("/byteseek/policy/deny", "") + byteseek/tool·edit_write("/tmp/byteseek_tool.txt", "hello\n") -> (tn, twc) + byteseek/tool·edit_replace("/tmp/byteseek_tool.txt", "hello", "world", 0) -> (td2, th2, tc2) + println("ALLOWED:", tc2) + byteseek/tool·edit_replace("/tmp/byteseek_tool.txt", "world", "again") -> (td3, th3, tc3) + println("ARG:", tc3) + kvspace·listlen("/byteseek/toolcall/") -> tcn + println("TOOLCALL:", tcn > 0) byteseek/memory·remember("theme", "byteseek 使用 kvlang 构建") byteseek/memory·remember("unrelated", "今天天气不错") byteseek/memory·recall("kvlang") -> m println("MEM:", m) + networld/edit·write("/tmp/byteseek_selftest.txt", "a b\n") -> (wn, wc) + networld/edit·replace("/tmp/byteseek_selftest.txt", "b", "c", 0) -> (ed, eh, ec) + networld/fs·readtext("/tmp/byteseek_selftest.txt") -> (et, _) + println("EDIT:", ed, ec, et) } }