Host-owned pseudo-futures:dispatch hoisting 与 hidden materialization
Status: Proposed; not implemented in the current runtime.
Related primary sources:
- LLMCompiler: An LLM Compiler for Parallel Function Calling
- APPL: A Prompt Programming Language for Harmonious Integration of Programs and Large Language Model Prompts
核心判断
普通 Agent Python 可以继续保留同步表面:
x = tools.search(query)
use(x)
内部把一次调用拆为两个 compiler/Host 阶段:
handle = submit(search, query)
...
x = materialize(handle)
这里的 handle 不是 Python Future,也不会绑定给用户变量 x。它只是 compiler 生成的隐藏 token,引用 Host 内的 request state。compiler pass 可以提前 submit,也可以把隐藏的同步 materialize 调用推迟到第一次必须观察结果的位置。用户不接触 Future、async、await 或 .result()。
但这要求 Host ABI 先支持 split-phase call。当前单次 host_call 会在 Host 中等待 Broker 返回结果;仅靠 AST rewrite 无法制造 overlap。
三层表示
Source surface synchronous tool call
Compiler plan Submit + Materialize
Guest derived code hidden token + ordinary blocking Host import
Host execution request table, scheduling, completion and waiting
初始 lowering 必须让两个节点相邻:
x = tools.search(q)
_h = __host_submit__("search", q)
x = __host_materialize__(_h)
__host_materialize__ 对 Guest/Python 来说只是一个普通同步函数:Host 已经完成时直接返回;尚未完成时由 Host 等待;失败时在这个调用位置返回错误并由 Guest helper 抛出对应异常。这个形态先保留当前等待位置,优化再分两步进行。
Host-owned pseudo-future
所有异步状态都留在 Host:
handle → submitted | running | ready | failed | cancelled | consumed
Host 负责:
- 启动 provider/Broker request;
- 维护并发和 provider budget;
- 保存完成结果或失败;
- 唤醒等待
materialize(handle)的 Guest; - Run 结束时 cancel/discard 未消费请求;
- 将每个动态 call occurrence 与自己的 terminal record 对齐。
Guest 只拥有一个不可解释的短 token。它不能查询 readiness、注册 callback、调用 Future API 或把 Host task object 泄漏给普通 Python。compiler 在 derived source 中控制 token 的全部定义和使用。
这也意味着“集中在 Host”是集中 scheduling、waiting 和 lifecycle,不自动表示把多个 calls 融成一个 provider batch。batch/fusion 仍是另一项优化,需要 per-item result、failure 和 retry 规则。
Hidden materialization sinking
x = tools.search(q)
pure_local_work()
use(x)
可变为:
_h = submit("search", q)
pure_local_work()
x = materialize(_h)
use(x)
compiler 中仍可把这个 pass 简称为 await sinking,但 generated Python 没有 await;移动的是一个隐藏的 blocking Host import。materialize 不能越过以下 barrier:
- 第一次读取、转换或比较结果;
- 依赖结果的 branch、loop 或函数调用;
- 原调用异常本应阻止的代码;
- 会让错误顺序或外部变化顺序可见的操作;
try/except/finally、context manager 和 cancellation boundary。
Dispatch hoisting
如果参数更早已经可用:
q = make_query(config)
local_work_1()
x = tools.search(q)
local_work_2()
use(x)
可以研究:
q = make_query(config)
_h = submit("search", q)
local_work_1()
# original logical occurrence
local_work_2()
x = materialize(_h)
use(x)
只有允许“未被程序使用时直接丢弃”的 pure work 或 immutable read 才可以提前 physical submit。普通 write、发送消息、支付、进程启动等不能靠延迟 materialize 把已经发生的外部变化隐藏掉。
如果中间代码调用 locals()、frame inspection、trace hook 或其他可以观察局部变量是否已经存在的机制,就必须在观察前 materialize,或者让 pass 拒绝该 region。不能通过在 x 中放一个 Python proxy 来假装结果已经存在。
LLMCompiler:借 ready-queue,不借 planner
LLMCompiler 的 Planner 生成带 $1、$2 placeholder 的 task DAG。Task Fetching Unit 在输入依赖满足后立即把 task 交给 Executor;论文还允许 Planner 流式产生 task,后面的 plan 尚未完成时,前面的 ready task 已经可以运行。
Pysolate 不需要另一个 LLM 猜 Python program 的 dependency graph。Python AST、tool-call metadata 和实际执行已经提供:
- value dependency;
- source order;
- branch/loop/exception structure;
- 当前动态 occurrence;
- tool 的 side-effect contract。
值得吸收的是一个 program-derived incremental ready graph:
new call occurrence
↓
arguments ready?
control path active?
early dispatch allowed?
resource budget available?
↓ yes
dispatch
↓
result ready ──→ unblock dependent resolve/call
因此“看见一点,满足条件就发一个”确实覆盖了 LLMCompiler 最有价值的 scheduling kernel,但证明来源不同:
LLMCompiler planner-declared DAG
Pysolate Python-derived dependencies + runtime occurrences
不复制完整 Python control flow
Host graph 不应成为第二个 Python interpreter。
- branch 尚未执行时,普通 branch 内 call 不进入 ready queue;
- Python 选择路径后,Guest 激活该路径上的 occurrence;
- loop 每次迭代创建新的 occurrence;
- 只有明确允许未使用 preparation 的 read/pure call,才可以在动态 reach 之前进入 speculative lane。
因此全程序 CFG 仍由 Python 执行。Host 只维护 tool-call/event 子图及其 ready frontier。由于 loop 会不断产生 occurrence,内部对象更准确地说是 incremental event graph,不必强行承诺全局静态 DAG。
建议的动态图节点与边
节点:
Submit(call occurrence)
Materialize(hidden token)
Commit(prepared effect) # 仅工具本身提供 prepare/commit 时
边:
data edge 参数或后续 call 依赖结果
control gate Python 已经激活该动态 occurrence
effect-order 必须保持的外部变化顺序
exception edge 失败必须在继续执行前可见
resource edge concurrency、deadline、provider budget
ready condition不是“所有参数有值”这一项,而是这些 gate 同时满足。
APPL:值得参考的具体机制
APPL 的 gen() 异步执行,返回的 StringFuture 尽量保持惰性;调用 str() 或无法继续惰性处理的方法时才 materialize。BooleanFuture 在 bool() 时同步,因此 branch 自然成为 demand barrier。APPL 还区分 new、copy、same、resume 四种 prompt-context passing 模式。
对 Pysolate 有价值的部分:
- first-demand synchronization:结果不是在 call 返回点自动等待,而是在第一次必须观察具体值时等待;
- value-specific deferred operations:APPL 可以把字符串拼接继续保留为 Future composition;Pysolate以后只能对有明确 rewrite law 的少数值操作采用相同思路;
- control-flow barrier:truthiness、comparison 和 branch 必须先获得结果;
- context independence:APPL 的
new/copy说明独立 context 更容易并行;same/resume则引入共享或历史状态依赖; - trace 只用于观察和 replay:它不能撤销已经发生的 side effect。
不应直接照搬:
- APPL 的异步行为是它自己的语言语义;普通同步 Python 没有自动接受这种 exception timing;
- 通用 Python Future proxy 会暴露 object identity、type、method dispatch 和 introspection 差异;
- stochastic model call 的发起顺序、provider batching、cancellation 和 response 都可能变化;
same/resumecontext 不应被当作无依赖 call。
因此 Pysolate 更适合由 compiler 在已知 use site 前插入隐藏的同步 materialize Host call,而不是把 Future object 暴露给 Agent Python。
prepare/commit 是另一层能力
如果某个工具本身提供:
prepare(args) -> prepared handle
commit(handle) -> external effect
则可以提前 prepare,在原始逻辑位置 commit。但这不是 runtime 自动把任意 write 变成 reversible effect;工具必须明确提供 staging、expiry、cancellation、single-commit 和 partial-failure 语义。
与当前实现的距离
Current:
semantic_pre_dispatch可以在 Python execution 之前启动一个受限 read;- Broker 拥有 tool execution 和 receipt;
- source/AST pass 可以生成 derived program;
- physical preparation 与 logical occurrence 已有分离概念。
Missing:
- split-phase Host ABI:
submit/materialize/cancel; - Run-private handle table;
- compiler-inserted hidden
materialize; - incremental call/event graph 和 ready queue;
- exception/control/effect barriers;
- orphan completion 与 cancellation terminal state;
- runtime cost model。
因此该设计不能写成已经实现。它是把 semantic pre-dispatch、LLMCompiler scheduling kernel 和 APPL first-demand synchronization 统一起来的候选下一层。
最小实验边界
第一轮只需要:
- 一个 read-only deterministic test tool;
submit/materialize两个 Host imports;- straight-line Python;
- hidden
materialize只跨过 pure scalar/local compute; - branch、loop、
try、write、model call 全部拒绝; - baseline 保持同步 call,比较结果、异常位置和 latency timeline。
如果这个 slice 没有真实 overlap 或 Host scheduling overhead 已经吃掉收益,就不扩展到完整 event graph。