函数执行脑(function execution brain)
前身:本 issue 原描述为「vthread 栈暂停 + dump 给 LLM 补充函数/指令,边补充边执行」。
本方案把它推广:暂停与补充不是异常补救,而是每个节点的常态展开算子(thinknext)。
一句话
推理 = 在 kvspace 里跑一棵可执行搜索树:帧是节点,节点坐标是 [stackid, stackorder],
函数调用是边,thinknext 是节点展开。栈即搜索路径,函数子树即搜索树本身——不另建树结构。
流程
- 空闲:进程起来后停在等待外部消息(
input),不预先生成任何东西。
- 生成:收到消息 → LLM 产出
{函数名, 函数体},函数体内恰好 1 条 thinknext。
- 入库:layout 该函数进
/lib(vet 闸门不通过即 error,不入库)。
- 执行:调用该函数名,入口帧坐标
[0,0]。
- 展开:函数体内的子函数序列逐层执行;因函数体内只有 1 条 thinknext,
每个节点恰好一步决策——函数体 = 一个决策点。
- 收束:退出函数栈后总结整个函数栈(不是单个函数),写
/vthread/{vid}/[stackid,stackorder].stackmem。
节点坐标 [stackid, stackorder]
把原先单一维度的 fid 二维化:一维管身份,一维管位置。
stackid —— 身份轴:全局单调递增,只增不减、退出不回收。每次「创建并进入子函数」
分配一个新 stackid。同一节点在整个执行期内只有一个 stackid,永不复用。
stackorder —— 位置轴:保持当前。它是该帧在当前调用栈中的序位(等价原 fid 的语义:
调用 +1,退出 -1),随弹栈回退。
- 写法与 kvlang 指令坐标
[s0,s1](行、列)同构:第一维定位是谁,第二维定位在哪。
- 节点路径:
/vthread/{vid}/[stackid,stackorder]/[stackid,stackorder]/…,逐段即调用链。
- 回溯目标
[target_stackid, target_stackorder]:身份轴锁定「哪一个」,位置轴确认「它在栈上哪一层」。
为什么这样切分
- 身份与位置分离后,「同一深度互相覆盖」的问题消失:stackmem 路径因
stackid 唯一而各写各的,
兄弟子树不再互踩。
- 同一位置被多次访问时:
stackorder 相同、stackid 不同 ⇒ 审计上能区分「同一层的第几次访问」。
- 动作 3(改写)落在身份轴上 ⇒ 改写历史可以按
stackid 记账、可回溯到具体那一次。
thinknext:唯一的展开算子
| # |
动作 |
语义 |
树搜索术语 |
| 1 |
继续 |
推进本帧 PC /vthread/{vid}/[stackid,stackorder]/[+1,0] |
continue(单步推进) |
| 2 |
下钻 |
创建新子函数并进入:分配新 stackid,stackorder +1 |
expand(生成子节点) |
| 3 |
改写 |
修改当前帧的下一条指令 |
node revision(在线改节点) |
| 4 |
回溯 |
改写已执行过的函数,回到 /vthread/{vid}/[target_stackid,target_stackorder] |
backtrack + backup |
用树的搜索术语看
映射表
| 搜索概念 |
本方案 |
| 状态 state |
帧 /vthread/{vid}/[stackid,stackorder](帧内 PC + 局部槽 = 局面) |
| 节点 node |
一次函数调用 = 一个帧 |
| 节点身份 |
stackid(单调递增,永不回收) |
| 深度 depth |
stackorder(调用栈内序位) |
| 根 root |
入口帧 [0,0] |
| 边 edge / operator |
进入子函数 |
| 分支因子 b |
thinknext 可选动作数(动作 2 可现场造任意函数 → b 无上界) |
| 展开 expansion |
thinknext 调 LLM 生成下一步 |
| 搜索策略 |
目前是纯 DFS(下钻 + 回溯),无启发式 |
| 前沿 frontier |
未完成的帧链(就是当前栈) |
| 回溯 backtracking |
动作 4 |
| 值回传 backup |
退出函数栈写 stackmem |
| 目标测试 goal test |
thinknext 判定任务达成 |
| 转置表 transposition |
stackmem 若按状态指纹索引,可复用同一子问题的结论 |
| 闭环检测 cycle check |
同一状态重复出现即闭环(图搜索而非树搜索) |
| AND-OR 树 |
函数体内的「子函数序列」= AND(全做);thinknext 多选一 = OR |
| 分层任务网络 HTN |
函数 = 复合任务,thinknext = 选方法,子函数 = 子任务分解 |
| options / SMDP |
子函数 = macro-action(有启动、内部策略、终止),stackmem = option 结果 |
这套方案的位置:最贴近 Tree-of-Thoughts(把 thought 换成可执行帧)与 HTN(LLM
现场造方法)。与 LATS / RAP 这类 MCTS + LLM 的差别是:现在只有单条 DFS 轨迹 + 事后总结,
没有 value(节点价值)、visits(访问计数)、rollout(多次试跑)——即还谈不上 best-first。
相对 ToT 的结构优势:树不需要额外的数据结构。kvspace 里帧路径本身就是调用链,
kvlanglayout·dump 能把 /lib 子树逆向成可运行 kvlang 源码并标注每条指令的 [s0,s1] 槽位——
搜索树天生可寻址、可持久、可 dump、可审计。
二维化之后新增的两条要求
- 坐标所指的节点必须还在树上。 kvlang 的 return 是「弹出当前帧、删其子树」:被弹掉的历史帧
连同它的 [stackid,stackorder] 坐标一起消失。动作 4 想「回到 targetfid 改一条指令」时,
坐标还在记录里、节点却已不在树里。所以需要一份不随弹帧消失的节点账本 / 搜索树副本
(落在帧子树之外,如 /byteseek/tree/{vid}/…),或者改弹帧语义。这是二维化的直接代价:
坐标稳定了,坐标指向的东西必须同样稳定。
- 闭环检测不能靠
stackid。 每次调用都分配新 stackid,所以「回到同一个状态」在坐标上
看是新节点。转置表 / 重复检测必须另用状态指纹(帧的 ‥lib + 局部槽摘要)。
身份轴解决的是冲突,不是去重——两件事。
落地前还必须定清楚的几处
- 动作 3/4 破坏「节点不可变」。 搜索的回溯要求被回访的节点可复现;原地改写会让同一
坐标在不同时刻语义不同。要么节点版本化(坐标 + 版本号),要么改写前存旧值(undo log)。
- stackmem 写在帧路径下会被删。 与上一条同源:
/vthread/{vid}/[stackid,stackorder].stackmem
随弹帧消失。要留存必须落在帧子树之外,或改弹帧语义。
- 「回溯」不是 goto。 kvlang 的 goto 是帧内跳转(只改 PC 的行号,帧不变);跨帧回到
目标坐标 = 弹掉中间帧 + 在该帧改写下一条指令(动作 4 = backtrack + 动作 3 的组合)。
命名与实现都要按 kvlang 语义来。
- 动作 1「继续」需要精确定义。 函数体只有 1 条指令时
[+1,0] 已越出函数体:「继续」到底
是结束本帧(return),还是执行父帧的下一个子函数?帧内序列长度 = 1 时,动作 1 与 return
重合——必须选定其一。
- 终止性。 缺 goal test 的树不是搜索,是无限生成。需要
stackorder 深度上限、节点预算、
LLM 调用预算,以及状态指纹级的重复检测。
- thinknext 必须落在 runtime 侧。 它要读全栈、调 LLM,再改 PC / 建子帧 / 改写指令——
本质是控制流 rwir(call / goto / return 的合体)。kv rwfunc 改不了调用者的帧与 PC。
LLM 调用可直接复用现成的 llm·raw(sys,user) -> text。
- 每节点一次 LLM 调用 = 成本 O(节点数)。 要做 beam / 多候选,得让 thinknext 一次生成
多个候选(b > 1),并在 stackmem 里存 value / visits 才谈得上 best-first。
- 命名对齐。 身份轴
stackid 符合主流惯例(全局唯一 id);位置轴 stackorder 在五大语言
里更常见的对应词是 depth / index,沿用需在文档里写明它就是调用栈内序位,避免被读成
「兄弟序号」。
完成定义
- 收到消息 → 生成首函数 → layout → 执行 → 逐层 thinknext → 退栈总结 stackmem,全程无人工干预。
- 四种动作各自可观测:节点坐标、动作类型、stackmem 落在确定的 KV 路径上,且
stackid 可唯一定位。
- 有界:
stackorder 深度 / 节点数 / LLM 调用三重预算,超限停机并留现场(PC 与状态留在 kvspace)。
函数执行脑(function execution brain)
一句话
推理 = 在 kvspace 里跑一棵可执行搜索树:帧是节点,节点坐标是
[stackid, stackorder],函数调用是边,thinknext 是节点展开。栈即搜索路径,函数子树即搜索树本身——不另建树结构。
流程
input),不预先生成任何东西。{函数名, 函数体},函数体内恰好 1 条thinknext。/lib(vet 闸门不通过即 error,不入库)。[0,0]。每个节点恰好一步决策——函数体 = 一个决策点。
/vthread/{vid}/[stackid,stackorder].stackmem。节点坐标
[stackid, stackorder]把原先单一维度的 fid 二维化:一维管身份,一维管位置。
stackid—— 身份轴:全局单调递增,只增不减、退出不回收。每次「创建并进入子函数」分配一个新
stackid。同一节点在整个执行期内只有一个stackid,永不复用。stackorder—— 位置轴:保持当前。它是该帧在当前调用栈中的序位(等价原 fid 的语义:调用 +1,退出 -1),随弹栈回退。
[s0,s1](行、列)同构:第一维定位是谁,第二维定位在哪。/vthread/{vid}/[stackid,stackorder]/[stackid,stackorder]/…,逐段即调用链。[target_stackid, target_stackorder]:身份轴锁定「哪一个」,位置轴确认「它在栈上哪一层」。为什么这样切分
stackid唯一而各写各的,兄弟子树不再互踩。
stackorder相同、stackid不同 ⇒ 审计上能区分「同一层的第几次访问」。stackid记账、可回溯到具体那一次。thinknext:唯一的展开算子
/vthread/{vid}/[stackid,stackorder]/[+1,0]stackid,stackorder+1/vthread/{vid}/[target_stackid,target_stackorder]用树的搜索术语看
映射表
/vthread/{vid}/[stackid,stackorder](帧内 PC + 局部槽 = 局面)stackid(单调递增,永不回收)stackorder(调用栈内序位)[0,0]这套方案的位置:最贴近 Tree-of-Thoughts(把 thought 换成可执行帧)与 HTN(LLM
现场造方法)。与 LATS / RAP 这类 MCTS + LLM 的差别是:现在只有单条 DFS 轨迹 + 事后总结,
没有 value(节点价值)、visits(访问计数)、rollout(多次试跑)——即还谈不上 best-first。
相对 ToT 的结构优势:树不需要额外的数据结构。kvspace 里帧路径本身就是调用链,
kvlanglayout·dump能把/lib子树逆向成可运行 kvlang 源码并标注每条指令的[s0,s1]槽位——搜索树天生可寻址、可持久、可 dump、可审计。
二维化之后新增的两条要求
连同它的
[stackid,stackorder]坐标一起消失。动作 4 想「回到 targetfid 改一条指令」时,坐标还在记录里、节点却已不在树里。所以需要一份不随弹帧消失的节点账本 / 搜索树副本
(落在帧子树之外,如
/byteseek/tree/{vid}/…),或者改弹帧语义。这是二维化的直接代价:坐标稳定了,坐标指向的东西必须同样稳定。
stackid。 每次调用都分配新stackid,所以「回到同一个状态」在坐标上看是新节点。转置表 / 重复检测必须另用状态指纹(帧的
‥lib+ 局部槽摘要)。身份轴解决的是冲突,不是去重——两件事。
落地前还必须定清楚的几处
坐标在不同时刻语义不同。要么节点版本化(坐标 + 版本号),要么改写前存旧值(undo log)。
/vthread/{vid}/[stackid,stackorder].stackmem随弹帧消失。要留存必须落在帧子树之外,或改弹帧语义。
目标坐标 = 弹掉中间帧 + 在该帧改写下一条指令(动作 4 = backtrack + 动作 3 的组合)。
命名与实现都要按 kvlang 语义来。
[+1,0]已越出函数体:「继续」到底是结束本帧(return),还是执行父帧的下一个子函数?帧内序列长度 = 1 时,动作 1 与 return
重合——必须选定其一。
stackorder深度上限、节点预算、LLM 调用预算,以及状态指纹级的重复检测。
本质是控制流 rwir(call / goto / return 的合体)。kv rwfunc 改不了调用者的帧与 PC。
LLM 调用可直接复用现成的
llm·raw(sys,user) -> text。多个候选(b > 1),并在 stackmem 里存 value / visits 才谈得上 best-first。
stackid符合主流惯例(全局唯一 id);位置轴stackorder在五大语言里更常见的对应词是 depth / index,沿用需在文档里写明它就是调用栈内序位,避免被读成
「兄弟序号」。
完成定义
stackid可唯一定位。stackorder深度 / 节点数 / LLM 调用三重预算,超限停机并留现场(PC 与状态留在 kvspace)。