原生插件与模块(Rust)
Agent OS 的扩展方式不仅限于 YAML 配置和脚本(JS/Rhai),还可以通过
Rust 原生扩展——在服务器进程中运行的已编译代码。
有两种形式,二者都源自同一个 contribute():
| 形式 | 是什么 | 如何接入 |
|---|---|---|
| linked 模块 | 链接进二进制文件的 Rust crate(rlib) |
server.yaml → modules.enabled |
| DLL / 插件 | 动态库(.dll / .so / .dylib) |
config/plugins.yaml、kind: dll_tool、kind: dll_hook |
两种形式都向扩展点贡献相同的插件内容:
| 类别 | 提供什么 | 如何接入 |
|---|---|---|
| 工具 | kind: dll_tool |
工具的 YAML 文件 |
| 钩子 | kind: dll_hook |
钩子的 YAML 文件 |
| 同步器 | type: plugin 到 notify: |
cron / 监视器 / 通道 |
| 数据源 | type: plugin 到 source: |
config/monitors.yaml |
| 通道 | kind: 到 channels: |
config/channels.yaml |
| HTTP 路由 | stable 对象 Route |
register_registry* / contribute_route |
| 注册表与元素 | register_registry* |
JSON 元素、stable 对象、服务 |
| Config-bundle | 编译进程序的配置文件 | data/builtin/<id>/ |
如何构建一个完整的模块(依赖图、服务、host 端口、 打包、测试)——参见 32-building-a-module。
核心理念:稳定的 C ABI + 轻量 SDK
插件链接的是一个小巧的 ABI 头文件
agent_os_plugin_api(零依赖,#![no_std]),或者链接符合人体工学的 SDK agent_os_sdk,后者做同样的事,但
传输中立(同一份代码既可用于 linked 也可用于 DLL)。语言可以是任意的
(C/C++/Zig/Go/cgo/Rust cdylib),但只有 Rust 有 SDK。
| Crate | 角色 |
|---|---|
agent_os_plugin_api |
稳定的 C-ABI「头文件」:#[repr(C)] 类型、AoHostApi、AoRegistrar、AO_ABI_VERSION |
agent_os_abi |
稳定接口:AStr/AoSlice、Handle、OwnedObject + 宏 #[abi_trait]/#[abi_struct]/abi_registry! |
agent_os_sdk |
轻量 SDK:RegistryHost、declare_module!、declare_plugin!、service(id)、Value、Json |
agent_os_web |
作为 stable 对象 Route 的 HTTP 路由 |
agent_os_host_api |
host 服务契约(agent_os.Log、.State、.Http、……) |
当前 ABI 版本为 v8(AO_ABI_VERSION 在 agent_os_plugin_api 中);host
接受版本在 [AO_ABI_MIN, AO_ABI_VERSION] 范围内的任何插件
(目前为 4..=8)。因此:
- 不需要操作系统源码——只需要头文件/SDK 和契约;
- rustc 版本对 C-ABI 不重要——但由于
agent_os_abi生成 Rust 原生 vtable,host 和模块需要用兼容的 工具链为同一平台编译(这与普通 DLL 一样是「按操作系统」的); - 语言可以是任意的(Rust
cdylib是一等公民)。
最小插件
# Cargo.toml
[package]
name = "my_plugin"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[features]
# `host` = linked-транспорт; DLL собирается с `--no-default-features`.
default = ["host"]
host = ["agent_os_sdk/host"]
[dependencies]
agent_os_sdk = { path = "…/agent_os_sdk" }
agent_os_abi = { path = "…/agent_os_abi" }
// src/lib.rs
use agent_os_sdk::{declare_plugin, Registrar};
fn init(reg: &Registrar) {
// Самоописание модуля (id + версия).
reg.descriptor(r#"{"id":"demo","version":"0.1.0"}"#);
// Объявить JSON-реестр и положить в него элемент.
reg.registry("demo.items", "{}");
reg.registry_item("demo.items", r#"{"title":"Hello"}"#);
// Вкомпилировать файл config-bundle (материализуется в data/builtin/demo/).
reg.bundle_file("config/demo.yaml", b"key: value\n");
}
declare_plugin!(init);
declare_plugin! 生成唯一的导出符号——
ao_plugin_init(host, reg) -> u32;host 在加载后调用它,而
返回值就是 ABI 版本(不匹配会在调用前被拒绝)。
符号 ao_module_registry(用于 stable 对象,见下文)由
abi_registry! 添加。
同一份 contribute() 的两种传输方式
agent_os_sdk::declare_module! 从同一个函数生成 既 DLL 入口点,
又 linked 实现 agent_os_modules::Module:
use agent_os_sdk::{declare_module, RegistryHost};
fn contribute(h: &mut dyn RegistryHost) {
h.data_registry("demo.items", "{}");
h.data_item("demo.items", r#"{"title":"Hello"}"#);
}
agent_os_sdk::declare_module!(DemoModule, "demo", contribute);
- linked 构建(
feature = "host",默认)产出pub struct DemoModule带有impl Module——host 通过server.yaml → modules.enabled引入模块。 - lean 构建(
--no-default-features)产出cdylib带有ao_plugin_init。
同一份代码和 module.yaml 在两种形式下描述模块。详细
解析——32-building-a-module。
入口点与注册
插件导出:
pub extern "C-unwind" fn ao_plugin_init(host: *const AoHostApi, reg: *const AoRegistrar) -> u32
reg 是 AoRegistrar,即注册函数表。以下是它的字段(raw
ABI;上面示例中的 SDK 包装 agent_os_sdk::Registrar 仅覆盖
注册表、元素、stable 对象、服务、bundle 和描述符)。按约定,
注册分为三组:
1.「Kinds」(兼容 v4)——按 kind 名称的行为:
reg.register_tool(name, schema, vtable) // тул (kind: dll_tool)
reg.register_hook(name, vtable) // хук (kind: dll_hook)
reg.register_source(name, vtable) // источник монитора
reg.register_sink(name, vtable) // синк
reg.register_channel(name, vtable) // входящий канал
reg.register_route(method, path, access, vtable) // HTTP-роут
2. Config 驱动的引擎(ABI v5)——YAML 中的 kind: <name> 解析为
已注册的 vtable:
reg.register_tool_engine(name, vtable)
reg.register_hook_engine(name, vtable)
reg.register_route_engine(name, vtable)
reg.register_script_fn(name, vtable)
reg.register_slot_provider(slot_id, schema_json)
reg.register_contribution(slot_id, item_json)
reg.register_module_lifecycle(module_id, vtable)
reg.register_module_descriptor(descriptor_json) // self-describing single-DLL
reg.register_bundle_file(rel_path, bytes)
3. 注册表(ABI v5/v7)——通用扩展点:
reg.register_registry(name, schema_json) // объявить реестр
reg.register_registry_item(name, item_json) // JSON-элемент
reg.register_registry_engine(registry, item, interface, vtable) // поведение по interface
reg.register_registry_stable(registry, item, vtable, handle) // готовый stable-объект (v7)
函数返回 0 = error::OK,否则返回错误码。ABI 版本为
AO_ABI_VERSION。
Host 服务
插件不直接访问内核,而是获得具名 host 服务。
传输中立的方式是 agent_os_sdk::service(id):在 linked 模块中它
从图中解析服务,在 DLL 中则通过 ABI get_service。类型化
包装为 agent_os_host_api::client::*:
if let Some(svc) = agent_os_sdk::service(agent_os_host_api::ids::LOG) {
if let Some(log) = agent_os_host_api::client::log(svc.handle()) {
let _ = log.log("info", "demo", "hello from plugin");
}
}
主要端口(完整列表见 agent_os_host_api::ids):
| id | 用途 |
|---|---|
agent_os.Log / agent_os.Config / agent_os.State / agent_os.Metrics |
日志、(已编辑的)配置、KV、指标 |
agent_os.System |
host 内省(version/build/templates/config) |
agent_os.Agents / agent_os.Tools / agent_os.Llm / agent_os.Script |
进程、工具、推理、脚本 |
agent_os.Http / agent_os.Secrets |
出站 HTTP(SSRF 策略)和 allowlist 密钥 |
agent_os.Fs / agent_os.Process |
根目录边界内的项目文件系统,以及沙箱下的命令执行(敏感) |
agent_os.Messages / agent_os.Events / agent_os.Sessions |
转录、事件日志、会话 |
agent_os.Tasks + agent_os.Job |
后台任务(sync step,运行时由 host 提供) |
agent_os.Cron / agent_os.Workflow / agent_os.Connectors |
功能服务 |
敏感端口(Secrets、Http、Messages、Sessions、control-plane
等)fail-closed:没有声明 capability 的模块看不到它们。Host
会在模块代码周围设置 capability 作用域(路由处理器、Job::step、
事件回调)。
无需 serde 的值与 JSON
为了读取配置/YAML,插件不需要链接 serde:host 自己解析
树(json_parse / yaml_parse,ABI v6),而 SDK 提供包装 Value:
let v = agent_os_sdk::Value::parse_json(r#"{"prefix":">> "}"#)?;
let prefix = v.get("prefix").and_then(|p| p.as_str()).unwrap_or_default();
输出方面——一个微小的 writer agent_os_sdk::Json(对象/数组 → 文本)。
内存契约
- 插件返回的字符串/缓冲区(响应体、
content、data、visual、notice、reply、cursor、inject/items数组): 插件通过agent_os_abi::{alloc_str, alloc_slice}(或 raw ABI 中的host.str_alloc/host.slice_alloc)分配它们,长度恰好为所需长度;host 自行释放。不能返回静态字符串字面量。 - 宿主返回的字符串(
value_str、llm→out、rows_col_name、run_turn→out):借用,仅在调用期间 有效——若要保存请复制。 - 空字符串(
len == 0/ptr == null)=「无值」。
Panic
所有函数指针都是 extern "C-unwind"(ABI 与 C 相同,但允许
unwind)。宿主将每次调用包裹在 catch_unwind 中,因此插件的 panic
不会使进程崩溃。SDK 宏(declare_plugin! / declare_module!)和
#[abi_trait] 会自行设置 panic-guard;不过最好还是在自己的
extern 函数内部捕获 panic 并返回错误码 / Err(String)。
连接到 OS
DLL:config/plugins.yaml
# config/plugins.yaml
data_dir: data/plugins # где живут БД плагинов (db_open)
plugins:
- plugins/echo.dll # загрузить при старте (регистрирует все kinds)
加载按路径幂等:如果多个 agent(或 tool/hook YAML)
引用同一个 DLL,它只加载一次,ao_plugin_init 只执行
一次,kinds 也只注册一次。
Linked 模块:server.yaml
# config/server.yaml
modules:
enabled: [docs, widget, analytics] # активировать на буте (+ requires транзитивно)
auto_install: false # не ходить в сеть на старте
enabled ≠ installed:列表只是「启动时激活什么」;安装
产物是单独的操作。Always-on 模块(kernel、web)隐式
启用。
Tool
# config/agents/<name>/tools/echo.yaml
kind: dll_tool
path: plugins/echo.dll # относительный путь от YAML
name: echo # опционально (по умолчанию — имя из плагина)
config: # опционально: доступно в create(config)
prefix: ">> "
max_len: 120
config: —— 任意 YAML/JSON,宿主将其传给 create(config);
插件读取它(SDK 中的 Value 或 raw ABI 中的 value_*)。config 可以是
null。
Hook
# config/agents/<name>/hooks/audit.yaml
kind: dll_hook
path: plugins/echo.dll
hook: audit # имя kind, зарегистрированного плагином
插件 hook 可以实现任意生命周期点(全部可选,
None = no-op):before_user_message、after_turn、after_loop、
before_tool_call、after_tool_result、before_inference、
after_assistant_message、on_token、before_context_evict、
on_session_created、on_session_closed、on_budget_threshold、
before_retry、before_syscall、on_spawn、on_terminate、on_error —— 它们
镜像 HookHandler
(参见 06-hooks-reference)。
Sync / source / channel
# в notify: cron-задачи, монитора, воркфлоу или канала
- type: plugin
kind: collect
config: { ... }
# config/monitors.yaml
monitors:
- name: example
source:
type: plugin
kind: counter
config: { ... }
template: ngu-agent
prompt: "{{items}}"
# config/channels.yaml
channels:
- kind: echo_channel
config:
template: ngu-agent
token_env: MY_TOKEN
HTTP 路由
现代方式是 stable 对象 agent_os_web::Route
(meta() + handle()),宿主以所需的 Access
(public / user / admin)挂载它。通过 ABI 注册
register_registry_stable 到 routes 注册表。Legacy 路径是
register_route(method, path, access, vtable) 配合 route_access::PUBLIC(0)、
TOKEN(1)或 ADMIN(2)。
信任与安全
原生插件是服务器进程中的机器码,无法像脚本那样
沙箱化(没有操作次数限制)。这与编译型工具处于同一信任级别。
只从可信来源加载插件;将 plugins.yaml / module.yaml 置于
受控状态(在仓库/部署中)。Capability 门控限制对敏感
端口的访问,但不会隔离代码。
ABI 版本管理
AO_ABI_VERSION—— 在ao_plugin_init中握手;宿主接受范围[AO_ABI_MIN, AO_ABI_VERSION](当前为4..=8)并拒绝不匹配。- ABI v2:在
AoHookVtable中新增了拦截回调。 - ABI v3:新增 output-guardrail
after_assistant_message。 - ABI v4:其余点的对等(
on_token、before_context_evict、 session lifecycle、on_budget_threshold、before_retry、before_syscall)。 - ABI v5:模块化扩展 —— config-driven engines
(
register_tool_engine/register_hook_engine/register_route_engine)、register_script_fn、slots、module lifecycle、host.download、genericregister_registry*。 - ABI v6:
json_parse/yaml_parse/value_free—— 结构化 数据,无需serde。 - ABI v7:
register_registry_stable—— 将 stable 对象的 raw(vtable, handle)传给宿主(trait id 和 destroy 在 vtable 头部)。 - ABI v8(当前):
get_service—— 获取具名 host 服务。 #[repr(C)]结构中的字段只追加到末尾;绝不 删除或重排。因此旧插件可以继续工作: 它们只读取自己已知的字段。