为什么需要面向 Agent 的语言?
Octoscript 面向一种不同的作者:模型会提出程序、读取诊断、修正输出,再请求宿主执行。目标是让「意图 → 程序 → 操作」有一份小巧、可检查、可执行的约定。安全性来自语言、运行时与宿主边界的共同设计。
为什么不直接生成 Python 或 JavaScript?
需要成熟软件生态,或者让开发者在隔离环境中运行通用代码时,Python 和 JavaScript 都是很好的选择。Octoscript 关注另一种场景:应用需要在自己的运行时内部接受生成的工作流,只开放少量明确的服务,而不把整个通用开发环境交给模型。
| 设计问题 | 直接采用通用语言运行时 | Octoscript 的选择 |
|---|---|---|
| 模型可以生成什么? | 语言和生态很大,应用还需定义允许使用的子集。 | 发布明确的规范语法,提供有界预检和结构化诊断。 |
| 权限从哪里来? | 取决于嵌入方式、沙箱、导入机制及暴露的 API。 | 工具由宿主注册,授权由宿主签发,源码不能给自己授权。 |
| 如何融入 UI 事件循环? | 应用自行提供运行时集成与调度。 | Rust 嵌入与宿主驱动的延迟调用支持让出和恢复执行。 |
| 如何限制工作量? | 运行时与隔离策略需要补齐相应限制。 | 明确限制源码、token、嵌套、指令、截止时间、字符串、栈、调用帧和受跟踪的堆。 |
| Agent 如何修错? | 诊断跨语言、包与应用 API 分散。 | 版本化 profile、无副作用检查、格式化、工作流审查与有界编辑工具。 |
这不是说 Python/JavaScript 无法安全使用,而是主动选择不同的约束:用更小、更容易描述和执行的语言表面,换取宿主对生成程序的控制。已有的 Python 或 JavaScript 服务仍然可以封装成宿主工具。
先设计生成闭环,再设计语法
Agent 先获得语言 profile 和宿主当前的工具目录,再生成源码或纯数据的工作流草案。检查器在不加载模块、不执行副作用的前提下返回结构化诊断。Agent 根据诊断修正提案,宿主再决定是否创建计划并签发能力。
语言 profile + 工具目录 + 用户意图
→ 源码/工作流草案
→ 无副作用检查与审查
→ 根据诊断修正
→ 宿主批准 + 有界授权
→ 执行/让出/恢复
→ 结果与可审计状态
语法通过不等于允许执行。 工具目录描述能力,不授予权限;工作流草案表达计划,不自动成为有执行权的计划。这些分离让模型能够编程,同时让应用继续掌握操作边界。
一次调用,经过哪些边界?
以应用提供的 text.echo 为例。模型只生成下面这段程序;text.echo 是演示工具,实际服务由应用注册。
use mod.tool
let task = "prepare release notes"
let result = tool.call("text.echo", task)
result
| 阶段 | 输入与产物 | 这一阶段真正证明什么? |
|---|---|---|
| 1. 描述语言 | octoscript profile 返回版本、规范入口、检查与运行预算、无副作用工具和授权模型。 | 说明可生成的语言,不创建运行时或授权。 |
| 2. 描述工具 | 宿主读取 CapabilityRuntime::tool_catalog(),向模型提供当前注册工具。 | 名称、参数和限制是接口说明;目录不保证调用者有执行权。 |
| 3. 检查源码 | octoscript check 检查规范 v0.2 语法,再检查与底层解析器的兼容性。 | 语法成立。不会加载模块、调用工具,也不证明业务结果正确。 |
| 4. 决定权限 | 应用根据用户请求、当前策略与调用参数选择工具和预算,签发 capability lease。 | 只有宿主签发的有效授权能让调用越过能力边界。 |
| 5. 预留并执行 | 每次实际调用检查工具名、有效授权、剩余次数和输入限制;JSON 工具还可执行 schema 校验。 | 授权通过后才进入 Rust 适配器,返回值也要满足输出限制。 |
| 6. 返回或恢复 | 本地调用返回;延迟调用让出控制,由宿主完成外部工作后恢复。 | 应用事件循环保持控制,脚本不能自行创造外部操作句柄或恢复凭证。 |
例如,目录列出了 text.echo,但当前 lease 只允许 calendar.read,上面的程序仍然被拒绝。允许调用一次也不意味着允许第二次;修改工具名、超出输入字节数,或尝试使用另一会话的延迟结果,都不能继承这次授权。
工具目录里,哪些字段真的重要?
目录来自当前宿主注册表,按名称稳定排序。模型应使用这份目录,不猜测系统里“应该存在”什么工具。
| 字段 / 概念 | 含义 | 应用应如何使用 |
|---|---|---|
| name、description | 固定工具名称及用途;名称是有界的小写 ASCII 标识符。 | 将用途解释给模型,按准确名称查找注册能力。 |
| envelope、dispatch | 文本或 JSON 输入输出;本地或外部调度方式。 | 选择 call / call_json 或延迟调用接口。 |
| 调用、尝试、字节预算 | 限制调用次数、外部操作尝试次数、输入输出大小。 | max_attempts 由宿主使用,不给脚本增加重试 API。 |
| input / output schema | JSON 的结构与类型约束。 | 仅当 contract_enforced: true 时,Rust 工具边界确实执行这些 schema;否则它们只是描述。文本工具始终为 false。 |
| deferred / stream 限制 | 延迟截止时间;分块数量、大小与脱敏后的总量。 | 约束宿主调度与工作线程,不为脚本开放任意计时器或流。 |
默认目录最多注册 128 个工具,序列化总量最多 512 KiB,宿主可收紧这些边界。注册能力、展示目录、向某个工作流授予能力,是三个分别发生的动作。
草案如何成为有权执行的计划?
模型可以提交如下 JSON。source 使用上一节的语法;这里的换行通过 JSON 转义表示。
{
"format_version": 1,
"steps": [
{
"id": "prepare",
"source": "use mod.tool\nlet note = tool.call(\"text.echo\", \"draft\")"
}
]
}
草案只接受 format_version、steps 和步骤内的 id、source;未知字段会被拒绝。不能在 JSON 中塞入 approved: true、lease、检查点、工具结果或外部操作 ID 来获得权限。入口最多接收 2 MiB JSON、1,024 个步骤和 1 MiB 解码后的源码总量。
WorkflowDraft::from_json → 解码有界数据,拒绝额外字段
review → 无副作用语法检查 + 直接工具调用提示
engine.plan_draft → 创建引擎拥有的计划,只记录 Planned
宿主选择逐步策略 → 决定允许的工具、参数边界和预算
engine.approve… → 把计划与宿主批准绑定
engine.execute → 每次调用仍检查实际权限
review 的调用提示适合帮助审阅,但不能解析别名、控制流或动态工具名,也可能被截断。宿主不能只从提示里提取工具名就自动授权。审批与运行时预留检查要共同约束真实调用。
多个步骤需要共享数据时,宿主另外提供只含 JSON 的 workflow.input 和 workflow.outputs.<step_id>。上下文最多 64 KiB、64 层;不允许函数、原生句柄、循环引用和非有限数字。输出不满足约束时,下一步不会开始。数据本身不会携带执行权。
出错、等待与重试分别由谁负责?
| 情况 | 行为 |
|---|---|
| 语法错误 | 检查器返回位置与诊断,模型修正源代码后重新提交。 |
| 普通脚本错误 | v0.2 的 try … catch … 可选择替代值;不会得到可操作的底层错误对象。 |
| 资源预算耗尽 | 运行中止。硬性资源错误不能被捕获后继续消耗预算。 |
| 外部调用等待 | 脚本通过延迟句柄等待,宿主驱动完成、截止时间和恢复。 |
| 外部副作用已经发生 | 捕获异常不会撤销付款或消息。幂等键、对账、重试和持久恢复由宿主适配器与工作流引擎负责。 |
这些界限使失败能够被解释和处置;它们不承诺任意外部服务具备事务或 exactly-once 语义。
小巧、安全、可靠、高效,分别意味着什么?
小巧: 生成和检查围绕一份规范的工作流语言展开。继承的解析器能够识别某种 UI 或 shader 语法,并不意味着工作流也允许使用它。UI 有单独且明确的入口。
安全: 默认没有工具权限。经过审查的 Rust 适配器暴露操作、受限输入输出与宿主选择的授权。独立运行时屏蔽继承来的 UI/调试及无界原生入口。资源耗尽会中止执行,硬性资源限制不是普通应用异常,不能被脚本捕获后继续消耗资源。
可靠: 把源码检查与副作用分开,把数据与执行权分开,把重试与重复执行的授权分开。持久状态、幂等与恢复策略由宿主负责。捕获一个异常,不意味着付款等外部操作已经回滚。
高效: 复用可嵌入的 Rust VM 与宿主服务,限制保留的数据,让延迟调用返回应用事件循环。目标是较小的集成负担和可约束的执行成本。这里不把设计目标包装成未经测量的 Python/JavaScript 性能、体积或 token 成本优势。
VM 上限覆盖的是它实际跟踪的存储和操作,并非整个进程的内存配额,也不能限制任意可信 Rust 适配器的内部分配。需要运行不可信原生代码时,仍需操作系统隔离。预检限制源码、token 和语法嵌套;执行限制指令、截止时间、栈、调用帧、字符串和受跟踪堆。宿主需要给自己的原生适配器另设资源与隔离策略。
为什么从 Splash 出发?
Makepad 的 Splash 已经提供 Rust 脚本引擎、对象组合、UI 集成与实时迭代能力。从这里出发,可以保留生成式应用行为和丰富 UI 表达之间的联系。
Octoscript 在这套基础上为生成工作流作专门设计:规范的生成语法、有界检查和执行、受控宿主绑定、可审查数据流、能力授权与面向 Agent 的工具。UI 路径则通过自己的宿主继续复用 Splash 组件组合能力。
Fork 维护在 OctoSense-org/makepad 的 octoscript 分支。Octoscript 通过固定版本的 makepad-script 依赖使用它,这个仓库没有再复制一份 VM。VM 加固进入共享 fork;工作流和 UI 可以共享实现,但不必共享 VM 实例、堆或宿主绑定。上游策略记录了来源与更新规则。
A2App:让提案变成可运行的应用
这里的 A2App 指 Agent 能描述行为、组合 UI、检查实际生成的组件、操作它们,并根据运行证据修正源码。模型面对的不仅是一份语言规范,还有可以查询和操控的运行中应用。
继续阅读 Splash、UI 宿主与 Agent 工具,以及两条从设计到应用的流程。