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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ test: boot
echo "$$out" | grep -q "ALLOWED: 0" && \
echo "$$out" | grep -q "ARG: 9" && \
echo "$$out" | grep -q "TOOLCALL: true" && \
echo "$$out" | grep -q "PAUSED: paused" && \
echo "$$out" | grep -q "STACK: true" && \
echo "$$out" | grep -q "RESUMED: ok" && \
echo "$$out" | grep -q "MARKERS: 1 1" && \
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}; \
Expand Down
2 changes: 1 addition & 1 deletion doc/substrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ byteseek 不再注册任何自有 Rust rwir。所需能力全部是 kvlang 标
| `print` / `println` / `cerr` / `input` | 标准 term rwir |
| `json·to` / `json·from` | 标准 json rwir |
| `http·call(method,header,url,body) -> resp` | 标准 http rwir |
| `kvlang·vet / ·format / ·layout / ·dump` | 标准 layout rwir |
| `kvlang·vet / ·format / ·layout / ·printlib` | 标准 layout rwir(printlib = 看 layout 结果,不读 .src) |
| `networld/proc·exec(args,envs) -> code, out, err` | 标准 networld rwir(子进程 + 捕获 @ 句柄) |
| `vthread·call(funckey)` | native builtin(同 vid 动态调用) |
| `string·* / kv·* / xv·*` | native builtin |
Expand Down
26 changes: 26 additions & 0 deletions lib/byteseek/ask.kv
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// byteseek 决策点:程序跑到需要「外部决策」(下一步该做什么)时,就地暂停本 vthread,
// 把控制权交回 harness;harness 用 kvlang·printstack 取活动栈问 LLM,把 LLM 生成的
// 下一步程序写进 /byteseek/next/fn,再把本 vthread 置回 running 续跑——
// ask·pause 恢复后就地 vthread·call 那段新程序,跑完回到原函数继续。
// 这是 #69「函数执行脑」的最小落点:停点是显式的,栈是交给 LLM 的事实。

lib byteseek {
lib ask {
// pause(reason):写暂停原因 → 暂停本 vthread → 恢复后读取 harness 注入的下一步并就地调用。
rwfunc pause(reason:[]char/utf32) -> () {
kvspace·set("/byteseek/next/reason", reason)
kvspace·set("/byteseek/next/fn", "")
vthread·setstatus("paused")
kvspace·has("/byteseek/next/fn") -> has
fn = ""
if (has) {
kvspace·get("/byteseek/next/fn") -> raw
char/utf32(raw) -> fn
}
if (fn != "") {
kvspace·set("/byteseek/next/fn", "")
vthread·call(fn)
}
}
}
}
9 changes: 9 additions & 0 deletions lib/byteseek/llm.kv
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ lib llm {
println("[llm] 生成代码:")
println(kv)
}
// 名字唯一化:同名会覆盖已入库的函数树——暂停中的帧还指着它,续跑/重跑都会踩。
kvspace·has("/lib/byteseek/session/" + name) -> taken
suffix = 1
while (taken) {
suffix + 1 -> suffix
string·formatint(suffix, 10) -> sn
name + "_" + sn -> name
kvspace·has("/lib/byteseek/session/" + name) -> taken
}
"lib byteseek {\nlib session {\nlib " + name + " {\n" + kv + "\nbyteseek/session/" + name + "·main()\n}\n}\n}\n" -> wrapped
kvlang·vet(wrapped) -> v
if (v != "ok") {
Expand Down
105 changes: 75 additions & 30 deletions lib/byteseek/main.kv
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,48 @@ lib byteseek {
println("bye.")
}

// byteseek·solve(prompt):生成 → 执行 → 失败回灌 → 有界重试(≤3 轮)。
// 失败回灌是 pi agent-loop 的 toolResult(isError) 的 KV 化:模型下一轮看得见自己错在哪
// byteseek·solve(prompt):生成 → 执行 → 失败回灌 | 停点决策)→ 有界重试(≤4 轮)。
// 失败回灌 = pi 的 toolResult(isError) 的 KV 化;停点决策 = 把活动栈交给 LLM 定下一步(#69)
rwfunc solve(prompt:[]char/utf32) -> () {
llm·call(prompt) -> entry
byteseek·run(entry) -> (kind, detail)
attempt = 1
while (kind != "ok" && attempt <= 3) {
byteseek·run(entry) -> (kind, detail, vid)
round = 1
while (kind != "ok" && round <= 4) {
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 == "paused") {
// 停点:把活动栈交给 LLM,让它只补「下一步」,由暂停中的 vthread 就地接上
kvlang·printstack(vid) -> stack
prompt + "\n\n[任务执行到停点,需要你决定下一步]\n" + stack + "\n[暂停原因] " + bd + "\n请只输出 <name>/<kv>:一段补充的 kvlang 程序,它会就地接在停点继续执行。" -> askprompt
llm·call(askprompt) -> next
string·find(next, "error") -> ne
string·find(next, "truncated") -> nt
if (ne == 0 || nt == 0) {
"vet" -> kind
next -> detail
} else {
byteseek·resume(vid, next)
byteseek·classify(vid) -> (kind, detail)
}
} else {
prompt + "\n\n[上轮执行未成功] " + kind + ":" + bd + "\n请修正后重新生成(只输出 <name>/<kv>)。" -> retry
llm·call(retry) -> entry
byteseek·run(entry) -> (kind, detail, vid)
}
round + 1 -> round
}
if (kind != "ok") {
println("[byteseek] 重试 3 轮仍未成功:", kind, "——", detail)
println("[byteseek] 4 轮内仍未成功:", kind, "——", detail)
}
}

// byteseek·run(entry) -> (kind, detail):在当前 vthread(同 vid)执行生成程序并记账。
// kind empty / truncated / vet / fail / ok
// byteseek·run(entry) -> (kind, detail, vid):开子 vthread 执行生成程序并记账。
// kind empty / truncated / vet / fail / runtime / paused / ok
// 工具级失败由程序按协议写 /byteseek/last/result("fail: 原因");没写 = ok。
rwfunc run(entry:[]char/utf32) -> (kind:[]char/utf32, detail:[]char/utf32) {
rwfunc run(entry:[]char/utf32) -> (kind:[]char/utf32, detail:[]char/utf32,
vid:[]char/utf32) {
"" -> kind
"" -> detail
"" -> vid
if (entry == "") {
"empty" -> kind
} else {
Expand All @@ -83,30 +101,57 @@ lib byteseek {
"入口不存在: " + 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·classify(vid) -> (kind, detail)
}
}
}
}
byteseek·record(kind, detail, entry)
}

// byteseek·classify(vid) -> (kind, detail):读子 vthread 的终态/停点与程序自报。
// error→runtime(带 /vthread/<vid>/‥error/msg);paused→paused(带暂停原因);
// 其余看 /byteseek/last/result:""/"ok"→ok,其他→fail。
rwfunc classify(vid:[]char/utf32) -> (kind:[]char/utf32, detail:[]char/utf32) {
"" -> kind
"" -> detail
"/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 {
if (st == "paused") {
"paused" -> kind
kvspace·has("/byteseek/next/reason") -> hr
if (hr) {
kvspace·get("/byteseek/next/reason") -> rr
char/utf32(rr) -> detail
}
} else {
kvspace·get("/byteseek/last/result") -> rep
if (rep == "" || rep == "ok") {
"ok" -> kind
} else {
"fail" -> kind
rep -> detail
}
}
}
}

// byteseek·resume(vid, fn):把 LLM 生成的下一步入口写进 /byteseek/next/fn,
// 把暂停中的 vthread 置回 running 再驱动——暂停点里的 ask·pause 恢复后会就地调用它。
rwfunc resume(vid:[]char/utf32, fn:[]char/utf32) -> () {
kvspace·set("/byteseek/next/fn", fn)
"/vthread/" + vid + "/‥status" -> sp
kvspace·set(sp, "running")
vthread·run(vid)
}

// byteseek·record(kind, detail, entry):一次执行的记录落 /byteseek/tool/<n>(可寻址、可复盘)。
rwfunc record(kind:[]char/utf32, detail:[]char/utf32, entry:[]char/utf32) -> () {
kvspace·listlen("/byteseek/tool/") -> n
Expand Down
23 changes: 23 additions & 0 deletions lib/byteseek/prompt.kv
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,33 @@ rwfunc main() -> () {
}
</kv>

示例3——用户说"读 /tmp/a.txt 判断它属于哪类内容,把结论写进 /tmp/kind"(判断得由你来做,写代码时你看不到文件内容):
<name>judge_file</name>
<kv>
rwfunc main() -> () {
networld/edit·read("/tmp/a.txt", 1, 0) -> (text, total, sz)
println(text)
byteseek/ask·pause("已读出内容,需要判断它属于哪类并决定写什么")
}
</kv>
注意:暂停之后**不用写后续步骤**——harness 会把当前活动栈交给 LLM,把你生成的"下一步"接回同一栈继续执行。

示例4——用户说"读 /tmp/b.txt,如果含 b 就替换成 c"(这是程序自己就能判断的事,不要停):
<name>replace_b</name>
<kv>
rwfunc main() -> () {
byteseek/tool·edit_replace("/tmp/b.txt", "b", "c", 0) -> (done, hit, code)
println(code)
}
</kv>

硬规则:
- 只写顶层 rwfunc main 定义,不要写 main() 调用。禁止 lib 块,禁止把代码包进别的结构,禁止 markdown 围栏。
- 工具清单见 system prompt 的《可用工具》段——那一段由 /byteseek/tools/ 树生成,只列真实存在的工具;不要调用清单以外的名字。
- 改文件必须走清单里的 byteseek/tool·edit_*(闸门 + 记账;replace 只在原文唯一命中时落盘);不要用 string·replace + networld/fs·writetext,也不要直调 networld/edit·* 绕过闸门。
- **停点由你自己规划**:写代码时你只能看到需求、看不到运行结果。凡是"下一步做什么"需要你先看过结果再判断的(读懂文件/报错/网页内容,再决定策略或写法),就用 byteseek/ask·pause("为什么需要决策") 停在那里——harness 会把活动栈交给 LLM,并把下一步接回同一栈继续。
- **程序自己能判断的就不要停**:条件分支、循环、字符串处理、工具返回码判断,直接写进代码(见示例4);每次停点都是一次 LLM 往返,只在该停的地方停。
- 需要语义判断时也不要自己内联调 LLM(llm·raw 之类):停点交给 harness,决策与当时的栈才留在可复盘的轨迹里。
- 若你判断任务没完成(例如工具返回非 0 的 code),把原因写进 `/byteseek/last/result`(格式 `fail: 原因`);byteseek 会把它回灌给你重试。成功不用写。
- 最终结果用 println(...) 打印。
- 如果需求后面附有 `[相关记忆]`(若干 `key = value` 行),这些是已沉淀在 kvspace 树里的记忆;优先利用其中与任务相关的事实或结论来生成代码。
Expand Down
1 change: 1 addition & 0 deletions lib/byteseek/tools.kv
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ lib byteseek {
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/byteseek.ask.pause", "byteseek/ask·pause(reason) —— 停点:需要先看到这一步的结果才能决定下一步时调用;本 vthread 就地暂停,harness 拿活动栈问 LLM,再把下一步接回同一栈续跑")
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 连接)")
Expand Down
22 changes: 22 additions & 0 deletions tests/selftest.kv
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ lib byteseek {
}
byteseek/session/boom·main()
}
lib waiter {
rwfunc main() -> () {
kvspace·set("/byteseek/selftest/before", "1")
byteseek/ask·pause("测试停点")
kvspace·set("/byteseek/selftest/after", "1")
}
}
lib step2 {
rwfunc main() -> () {
kvspace·set("/byteseek/selftest/injected", "1")
}
}
}
}

Expand Down Expand Up @@ -62,6 +74,16 @@ lib selftest {
println("ARG:", tc3)
kvspace·listlen("/byteseek/toolcall/") -> tcn
println("TOOLCALL:", tcn > 0)
byteseek·run("byteseek/session/waiter·main") -> (pk, pd, pvid)
println("PAUSED:", pk)
kvlang·printstack(pvid) -> pst
println("STACK:", string·contains(pst, "status=paused"))
byteseek·resume(pvid, "byteseek/session/step2·main")
byteseek·classify(pvid) -> (rk, rd)
println("RESUMED:", rk)
kvspace·get("/byteseek/selftest/injected") -> inj
kvspace·get("/byteseek/selftest/after") -> aft
println("MARKERS:", inj, aft)
byteseek/memory·remember("theme", "byteseek 使用 kvlang 构建")
byteseek/memory·remember("unrelated", "今天天气不错")
byteseek/memory·recall("kvlang") -> m
Expand Down
40 changes: 40 additions & 0 deletions tests/stopplan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# 停点规划回归:模型是否**自己**判断该不该停(需要真实 LLM)。
# 用例1 确定性任务(程序自己能判断)→ 期望 0 次停点
# 用例2 需要语义判断的任务(写代码时看不到内容)→ 期望 ≥1 次停点
# 用法:tests/stopplan.sh [任务名...](默认两个用例都跑)
set -uo pipefail
cd "$(dirname "$0")/.."
export KVSPACE=${KVSPACE:-redis://127.0.0.1:6379}
export KVLANG_LIB=lib

D=/tmp/stopplan
mkdir -p "$D"
printf 'x\nb\ny\n' > "$D/b.txt"
printf 'def f(x):\n return x + 1\n' > "$D/a.txt"

KVLANG_LIB=lib kvlang >/dev/null 2>&1 || { echo "byteseek 引导失败"; exit 2; }

run_case() { # $1=用例名 $2=期望(0|1) $3=任务
local name=$1 want=$2 task=$3 log="/tmp/stopplan-$1.log"
printf '%s\n' "$task" | timeout 600 kvlang byteseek·main >"$log" 2>&1
local got
got=$(grep -c "任务执行到停点" "$log")
local ok="FAIL"
if [ "$want" = "0" ] && [ "$got" = "0" ]; then ok="PASS"; fi
if [ "$want" = "1" ] && [ "$got" -ge 1 ]; then ok="PASS"; fi
printf '%s %-14s 停点=%s(期望%s) log=%s\n' "$ok" "$name" "$got" "$want" "$log"
}

if [ $# -gt 0 ]; then
for n in "$@"; do
case "$n" in
deterministic) run_case deterministic 0 "读 $D/b.txt,如果含 b 就替换成 c" ;;
judgement) run_case judgement 1 "读 $D/a.txt,判断它属于哪类内容(代码/散文/数据),把结论写进 $D/kind" ;;
*) echo "未知用例:$n" ;;
esac
done
else
run_case deterministic 0 "读 $D/b.txt,如果含 b 就替换成 c"
run_case judgement 1 "读 $D/a.txt,判断它属于哪类内容(代码/散文/数据),把结论写进 $D/kind"
fi
Loading