Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
51 changes: 45 additions & 6 deletions doc/substrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name> 浓缩成 ·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/*
```

## 引导与运行
Expand All @@ -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`
前缀),不是文件路径。

Expand Down Expand Up @@ -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」。

Expand All @@ -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/<n> = "kind \| detail \| entry"` | 可寻址、可复盘;detail 截断到 300 字 |
| 用量 | `/byteseek/usage/<n>` + `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/<vid>/‥error/msg`)/ `fail`(程序自报失败)/ `ok` / `empty`。

配套 runtime 语义:`vthread·run(vid)` 下**子 vthread 自己失败不再冒泡杀死父**——监督者活着读
`/vthread/<vid>/‥status` 与 `‥error/msg`(一个子任务失败不该结束整条会话)。

落盘是原子的:`networld/edit·write/replace/multi` 都走 `networld/edit·commit` —— 写 `<p>.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 捕获全链路通过。
39 changes: 36 additions & 3 deletions lib/byteseek/llm.kv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 程序。严格按系统提示的格式输出,只输出 <name>/<kv> 两段,不要解释:\n\n" + userinput -> user
sys -> /tmp/esc/sys
json·to(/tmp/esc/sys) -> sys_j
Expand All @@ -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 截断,请重发完整的 <name>/<kv>" -> entry
return
}
if (content == "") {
"error: LLM 响应无内容: " + resp -> entry
return
Expand Down Expand Up @@ -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
Expand All @@ -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/<n>(记账入树,可复盘)。
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)
}
}
110 changes: 100 additions & 10 deletions lib/byteseek/main.kv
Original file line number Diff line number Diff line change
Expand Up @@ -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请修正后重新生成(只输出 <name>/<kv>)。" -> 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/<n>(可寻址、可复盘)。
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)
}
}
29 changes: 2 additions & 27 deletions lib/byteseek/memgen.kv
Original file line number Diff line number Diff line change
@@ -1,39 +1,14 @@
// byteseek 能力记忆生成器(全 kvlang):把 /lib 下各目录的能力浓缩成一条 ·mem 记忆 key。
// 后缀约定:目录 /lib/<name> 的能力记忆存在 key /lib/<name>.mem。
// fnlist(name) —— 枚举 /lib/<name>· 下的函数/常量名(.src 条目去后缀,目录条目跳过)。
// (能力名枚举用 kvlang stdlib 的 kvlang/reflect·fnlist)
// summarize(name) —— 若 /lib/<name>·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
Expand Down
28 changes: 6 additions & 22 deletions lib/byteseek/memory.kv
Original file line number Diff line number Diff line change
Expand Up @@ -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/<cat> 新建。
// 匹配规则:query 与 key 互相包含,或 query 与 value 互相包含(子串匹配,大小写敏感)。
lib byteseek {
Expand Down Expand Up @@ -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) {
Expand Down
Loading
Loading