以下的内容是上述这个"外置"experience之外、我在尝试“内置experience至codex中”遇到的现实问题:
8月底,我开始根据Codex Apache2.0构建一个 “Experience” 机制,它遇到了一个显著的边界,且这个边界不仅仅是在codex上,DSH亦然如此。这也让我产生了这个疑惑:这个边界很像是刻意设置的,并不是我没找到正确的扩展点。而这个边界的初衷就来自于“llm控制一切”。
我观察到它有这样的特征(下面以对codex的开源改造为案例):
codex-rs/hooks 暴露了一组 hook 扩展点,包括:
-
PreToolUse
-
PostToolUse
-
PermissionRequest
-
UserPromptSubmit
-
Stop
-
PreCompact
-
SubagentStart
-
等等。
在 core/src/hook_runtime.rs 中,PreToolUseHookResult 定义为:
pub(crate) enum PreToolUseHookResult {
Continue { updated_input: Option<Value> },
Blocked(String),
}
底层的 PreToolUseOutcome 也携带类似的信息:
-
should_block
-
block_reason
-
additional_contexts
-
updated_input
因此,PreToolUse 可以:
-
阻止一个工具调用;
-
修改工具调用的输入;
-
向上下文增加信息;
但它似乎没有一种“返回工具结果”的状态。
也就是说,扩展可以影响工具是否执行以及以什么参数执行,但不能自己完成这个动作,然后把一个经过验证的结果直接交给模型,作为原本工具调用的结果。对于experience来说,这一步被系统性掐断了。
我注意到其中一些事件名称和 payload 字段——例如 tool_name、tool_input、tool_use_id、transcript_path、permission_mode、matcher aliases,以及 ClaudeHooksEngine——看起来与 Claude Code 的 hook protocol 有较强的对应关系。
所以我逐渐产生了这样的猜测:这是否意味着这里本身就是一个兼容性扩展面(compatibility surface),而不一定是 Codex 原生的扩展架构?这个架构是基于什么样的思想产生的?是不是在建设之初、其建设思想就没有考虑过让某些情况无需llm的场景?
PreToolUse 不允许“工具结果替换(tool-result substitution)”,是刻意设计的吗?
这里的 “tool-result substitution” 指的是:
扩展自己完成原本工具调用所要完成的工作,并验证其结果,然后跳过真正的工具执行,直接把这个结果作为该 tool_use 的结果返回给模型。
如果这是有意设计的,那么这种能力应该属于哪里?
例如:
另外,如果一个已经验证过的状态转换被未来任务复用,它的生命周期应该是:
-
thread-scoped?
-
agent-scoped?
这种复用结果是否必须是 durable / replayable 的,还是可以只是一次性的 transient optimization?
我对于内置Experience的理解是“时间维度下的LLM”、“LLM在过去成功下的复现”
在我的设计思路中,Experience可以被内置到agent中,也可以外置负责“连接agent”,但开发体验下来,内置无疑是更好的路径。Experience 并不是 RAG、MCP、Skill,也不是对历史上下文进行压缩。
它记录的是:
过去已经成功完成并经过验证的工作路径,并使这些路径能够在未来相似任务中被复用。
可以粗略表示为:
过去的任务
↓
LLM 决策
↓
执行动作
↓
验证成功
↓
Experience
↓
未来的相似任务
↓
复用过去已经成功的路径
↓
而不是再次让 LLM 从头重复推理
这里的 Experience 来源于模型过去自己的工作,因此我并不把它视为一个独立的 planner,也不是要取代当前 LLM。
当前 LLM 仍然负责它没有经验覆盖的部分,也可以在需要时主动检索和使用 Experience。
下面是我做的具体事情:
在最初的内置尝试失败后(9月7日左右),我决定先实现一个外置的 Experience prototype。也就是本贴的内容。
之后,我进一步把它接到了 Codex fork 中真实的工具 dispatch 边界:
LLM FunctionCall
↓
Experience matching
↓
Experience execution
↓
postcondition verification
↓
FunctionCallOutput
↓
LLM continues
也就是说,我实际验证的是:
在真正 dispatch 工具之前,可以识别一个已经存在的 Experience,让 Experience 完成工作、验证结果,然后向 LLM 注入一个 FunctionCallOutput,使 LLM 继续执行后续任务。
在一个很小的 synthetic domain 中,我用少量 Experience segments 验证了这条机制。
这个测试的重点并不是 workload-level 的 token benchmark——测试本身是刻意构造的,Experience 执行路径中没有模型调用。
因此,它证明的是:
Experience 可以在模型不参与的情况下完成一个已经被编码并验证过的动作路径。
至于真实 workload 中究竟能够节省多少 token、降低多少 latency,目前我还没有做严格的 benchmark。因为这需要不少资源,而我是一个个人开发者,这不严谨但很现实。
在此前的外置版本中,我观察到的方向与预期一致:在小规模测试下,复用已有 Experience 时 token 消耗和延迟都有下降,同时没有看到明显的成功率下降。
但我不会把这当作正式的性能结论。
边界问题让我感觉很棘手:
如上面所提到的,外置版本在功能上是可以工作的。我也有视频演示了这个过程。
但当我重新决定,尝试把它更深入地嵌入 Codex fork 时,遇到了几个实际的架构边界:
-
本地修改会受到官方 build / update 流程的影响;
-
官方 UI 并不知道 Experience 的存在;
-
Experience 需要了解 session、tool、workspace 和 lifecycle 等信息,而单纯的 hook 边界并没有覆盖这些语义。
因此,我最终把 fork 中的实现收敛成了一个 reference implementation,并进一步使用公开的 app-server 接口验证 Codex 的 session / lifecycle / tool / workspace 能力。
收回开头,这也让我怀疑:
我遇到的问题不是“没有找到正确的扩展点”,而是目前主流Agent,如Codex 本身有意把这个能力隔离在当前 hook 边界之外。
所以我在想:
1. PreToolUse 只能 gate / rewrite,而不能替换 tool result,这是到底是不是所有agent有意设计的?还是说它仅仅是某个团队某种固有思想的产物,而后继者们纷纷仅考虑跟随而没有想过这个问题?
或者,这个行为主要是因为它遵循了某种既有的 hook protocol / compatibility model?
2. 如果这是有意的,那我们的各种agent,如codex是否已经存在某种抽象,可以允许一个 extension 自己完成工作,然后返回这个 tool call 的 outcome?
如果存在,我目前可能只是没有找到正确的入口。
3. 如果不存在,那么“extension 做掉工具本身,并将结果返回给模型”是否本来就属于当前主流agent明确不支持的范围?
我也不是说一定要推动某一个具体设计,因为我自己也认为,experience是一个在成熟期的合适作品,在新奇开拓期并不是所有个人都依赖它(但我认为很多企业很需要)。
不过,既然我们都很清楚,依靠出售token并不是Agent应用的合适未来的话,那为什么我们不出售涵盖experience的打包方案为他们提供切实的路径呢?