这个小 demo 用普通 Runner.Run 执行真实 CPython Wasm:Python 从 Host 读取本地报告,依据报告内容调用模拟通知服务,再根据 Host 返回的结构化状态打印结果。它适合用几分钟解释 Spine 的普通调用链与能力边界。
它是静态程序信息接口示例:没有 LLM、prompt、benchmark,也不代表新增了 runtime 语义。Host 工具、场景状态和评测记录都属于 cmd/walkthrough;Spine 仍只负责已有的 manifest 注入、Python 工具调用和普通 Run。
运行
需要已有 dist/spine.wasm;脚本不会构建或替换 Guest artifact。go run 在依赖未缓存时可能获取 Go 依赖,正式演示前先准备缓存。Guest 构建见 构建与来源。脚本从任意当前目录启动:
bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario normal
bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario denied
bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario unknown
bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario all
bash /path/to/pysolate-spine/demos/walkthrough.sh -h
参数遵循 Go flag 惯例:-scenario 接受 normal、denied、unknown、all,默认是 all;-wasm 可指向另一个已有 artifact。也可从仓库根目录运行 go run ./cmd/walkthrough -scenario all。帮助中的说明指出了默认 artifact 和“不自动构建/下载”的约束。
每次执行会展示同一段 Python source、简短 Host 调用事件、Guest 输出和结果。all 会按 normal、denied、unknown 顺序分别创建 Runner 并各自普通 Run。
逐步读代码
examples/notification.py中的get_report()和send_notification(...)是 Host 注入的具名 Python 函数;具体函数体不是 Python import,而是由 Runner 的 manifest 提供。Python 用报告的title和items拼出消息,并对sent、denied、unknown分支处理。未知状态显式报错;unknown 分支只说确认状态不明,并且不重试。cmd/walkthrough/main.go的notificationHost.manifest()注册两项工具。get_report返回结构化报告;send_notification校验实际收到的参数,再按所选场景返回结构化状态。normal 表示本地模拟服务完成;denied 在 dispatch 前拒绝;unknown 表示模拟 dispatch 已发生但确认丢失。这里不调用真实通知服务,不产生外部效果。runScenario用spine.New建 Runner,随后调用runner.Run(ctx, source, inputs)。它打印 Host 事件、Guest stdout 和 Run 结果。普通 Run 没有开启 PLM、prefix 或 prepared 模式。serviceDelivered是 Host 侧独立评测状态,只在最后以明确标记输出,不会放进工具返回值、Guest inputs 或 Guest result。因而 unknown 对 Guest 来说确实未知;演示者可以独立看到模拟服务是否已 dispatch。
关键源码:
cmd/walkthrough/main.go:CLI、Host manifest、场景状态和输出。cmd/walkthrough/main_test.go:真实 Guest 场景测试、调用次数/参数和状态断言。examples/notification.py:被真实 Guest 执行的同一份 Python 程序。demos/walkthrough.sh:从任意当前目录定位仓库根并启动 Go CLI。bridge.go:现有Tool/ToolSpec/ManifestAPI 与 Host 调用边界。runner.go:现有New和普通Run路径。
沿一次调用讲:文件打开顺序
先只运行 ./demos/walkthrough.sh -scenario normal,不要一开始展开 PLM、prefix、COW。
- 打开 notification.py:
get_report没有在 Python 里定义,它从哪来?消息拼接和结果分支仍在 Python 内执行。 - 打开 main.go 的
manifest()和runScenario():应用注册两个 Go 函数,把源码与输入交给spine.New和Run。scenario 只给 Host,不作为 Guest 输入。 - 打开 runner.go 的
New:注册 WASI 与spine.call/prepare/resolveimports,编译 CPython Wasm。这里编译解释器 artifact,不是用户 Python。 - 同文件的
Run→run:建立本轮状态,将 source、inputs 和工具 manifest 编码,创建 Guest,经callWithBytes调用 Guestexecute。Runner 的编译代码可以复用,本轮 Guest 则单独创建和释放。 - 打开 runtime.c:
execute把 ABI 请求转入 CPython;python_call把 Python 请求送到 Host import。这层处理字节、缓冲与 Python 对象,不作业务决策。 - 打开 bootstrap.py 的
execute:为 manifest 中每个名称构造make_tool代理,再compile/exec用户源码。invoke_tool把 tool 名与 args 编码,交给_spine.call。 - 打开 bridge.go 的
hostCall→readRequest→invoke:检查 ABI 内存范围,按 Host manifest 查工具并执行。Python 代理不是授权边界;业务权限检查放在注册的实现里。 - 结果沿 bridge → C → Python
decode返回,Python 继续执行,最终把result交回 Go。Runner 收尾并关闭 Guest。
Python send_notification(message)
→ bootstrap.invoke_tool:JSON 编码
→ C python_call:Python bytes / Wasm ABI
→ Go bridge.hostCall → invoke:Host manifest 查找
→ 应用 send_notification:校验参数、派发或拒绝
← JSON {value: {status: ...}}
← Python decode:取得结构化状态
→ Python 分支,写入 result
→ bootstrap.execute → C execute → Go Output
第一遍回答“控制流在哪里、数据怎样过边界、谁拥有权限”。第二遍切 denied 和 unknown:两者都没有确认成功,但只有前者可以确定没有派发。不要把业务状态返回与 Go/Python 异常混为一谈。
ASCII 调用流程
CLI -scenario
|
v
cmd/walkthrough: 选择场景,注册 Manifest
| |
| +--> Host 模拟器保存 serviceDelivered
v
spine.New(existing Wasm, Manifest)
|
v
Runner.Run(同一份 Python source, {})
|
+--> Guest: get_report() --------> Host: 结构化本地报告
| |
+<-------------------------------------+
|
+--> Guest: send_notification(message)
| |
| +--> Host 返回 sent / denied / unknown
| (denied 不 dispatch;unknown 已 dispatch、确认丢失)
|
+--> Guest 按 status 输出;unknown 不宣称成功且不重试
|
+--> Run result + Guest stdout
独立于 Guest:Host 输出 evaluation-only serviceDelivered
Mermaid(可选)
支持 Mermaid 的阅读器可用下面的图;上面的 ASCII 图不依赖网络或图渲染器。
flowchart TD
A[CLI scenario] --> B[cmd 注册 Manifest]
B --> C[spine.New + 普通 Runner.Run]
C --> D[Guest get_report]
D --> E[Host 返回结构化本地报告]
E --> F[Guest 拼消息并调用 send_notification]
F --> G{Host 模拟结果}
G -->|normal| H[sent]
G -->|denied| I[未 dispatch,denied]
G -->|unknown| J[已 dispatch,确认丢失]
H --> K[Guest 按状态输出]
I --> K
J --> K[Guest 不宣称成功,不重试]
G -. 独立评测,不进入 Guest .-> L[serviceDelivered]
结果怎么理解
| 场景 | Host 工具结果(Guest 可知) | 模拟服务状态(Host 评测) | Guest 的说法 |
|---|---|---|---|
normal |
sent |
已 dispatch | 通知已发送 |
denied |
denied |
未 dispatch | Host 拒绝,未发送 |
unknown |
unknown |
已 dispatch,确认丢失 | 结果未知,不自动重试 |
Host 返回值结构化,是为了让 Python 根据真实可见信息做分支;它不是一套新的 Spine 错误/投递语义。这里的 sent、拒绝和确认丢失都只是本地 demo 约定。独立评测字段不能被解释为 Guest 知道了通知是否送达。
新 idea 的关联与边界
这个例子把“工具调用结果”和“模拟服务真实状态”放在不同视角。它连接未来的问题:固定工具能力和权限,给模型哪些效果契约与结果确定性信息、何时提供,才能减少误操作与无效重试?
- 这里实际演示的是第一个准备步骤:通过已有 ABI 传递明确的 provider 状态,并保证测试真值不进入 Guest。
- 当前 Python 的判断是人写好的,不能用这个例子声称模型学会了正确处理未知状态。
- 效果契约、按需查询与自动反馈的对照尚未实现;后续应保持工具和恢复策略一致,再比较信息方案。
- 不添加通用证据整理器,不让 runtime 推断业务策略。
sent/denied/unknown属于这个 Host provider 的约定。 - unknown 由本地模拟器控制,不是断电、真实网络故障或进程重启实验。它不能证明 exactly-once,也不能消除外部结果不确定性。
它不演示 PLM 提前读取、prefix 流式源码、并发工具调用、真实通知发送、LLM agent 或 benchmark。实际工具错误会让 Guest 调用失败;示例不会把错误变成成功状态。运行时与教学 demo 的边界不变:新增的业务含义全部留在 cmd/walkthrough。