原生插件与模块(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)。因此:

最小插件

# 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);

同一份代码和 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(对象/数组 → 文本)。

内存契约

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 版本管理