Rust 模块(linked 与 DLL)

模块是 Agent OS 的打包与扩展单元。它可以向其中贡献 元素类型、注册表及其中的元素、配置包、服务、 HTTP 路由、后台任务和原生库——并打包为 linked crate(链接进二进制文件),或打包为 DLL(.dll/.so/.dylib), 在运行时加载。同一个 contribute() 管理这两种 传输方式。

这是一份实用指南。模块系统的活文档真相来源是 crates/module-system/runtime/agent_os_modules/README.md;设计与论证见 内部笔记 docs/22-modules.md、docs/25-module-abi.md、 docs/26-feature-modules.md、docs/27-core-modularization.md、 docs/30-dll-ready-modules.md(不在网站菜单中显示)。

32.1 Crate

Crate 角色
agent_os_modules 组合模型:Module、ModuleBuilder、图、注册表、元素、服务、Access、module.yaml、ModuleLifecycle。不含 axum/kernel
agent_os_abi 稳定接口:AStr/AoSlice、Handle、OwnedObject、VtableHeader + 宏 #[abi_trait]/#[abi_struct]/abi_registry!
agent_os_abi_macro 这些宏的实现
agent_os_sdk 轻量 SDK:RegistryHost、declare_module!、declare_plugin!、service(id)、Value、Json
agent_os_web 作为 stable 对象 Route 的 HTTP 路由 + WebModule(注册表 routes 的所有者)
agent_os_host_api host 端口契约(agent_os.Log、.State、.Http、……)+ client::*
agent_os_plugin_api 稳定的 C-ABI「头」(用于 raw/legacy 插件)
agent_os_modules_runtime 驱动:激活/停用、state.json、version-manifest、bridge DLL
agent_os_plugin_host DLL 加载以及 ABI → 宿主类型适配器
agent_os_module_install 工件安装(download + sha256 + zip/tar.gz)、bundle 物化

DLL 模块的依赖规则:仅限 ABI-safe crate。禁止 agent_os_core、agent_os_tools、agent_web_bot、tokio、axum/hyper。 改用 host 端口。详见 docs/30-dll-ready-modules.md §2.2。

32.2 特征 Module 与 ModuleBuilder

use agent_os_modules::{Module, ModuleBuilder, RegistryType};
use serde_json::json;

struct Analytics;

impl Module for Analytics {
    fn id(&self) -> &str { "analytics" }
    fn version(&self) -> &str { "1.2.0" }          // semver, по умолчанию "0.0.0"
    fn requires(&self) -> Vec<String> { vec!["admin".into()] }
    fn capabilities(&self) -> Vec<String> { vec!["agent_os.Secrets".into()] }

    fn register(&self, b: &mut ModuleBuilder) {
        b.declare_type(RegistryType::data("analytics.panel"));
        b.provide_registry("admin.panels", "analytics.panel");
        b.contribute("admin.panels", json!({ "title": "Аналитика" }));
    }
}

构建生命周期分为三个阶段:

  1. register —— 在 ModuleBuilder 中声明贡献(唯一必需的 阶段)。
  2. wire(&mut ModuleBuilder, &WireContext) —— 依赖最终图的可选贡献 (ctx.is_enabled、ctx.has_registry、ctx.has_service、……)。
  3. bind(&BindContext) —— 持有运行期间所需的服务 (ctx.service(id) → Arc<OwnedObject>),按拓扑顺序排在 wire 之后。

ModuleBuilder:

方法 用途
declare_type(RegistryType) 声明新的元素类型
provide_registry(id, type_id) 声明类型 type_id 的注册表
provide_data_registry(id) / provide_slot(id) JSON-data 元素注册表
provide_stable_registry(id, interface) 接口 stable 对象注册表
contribute(id, json) 贡献 JSON 数据
contribute_stable(id, OwnedObject) 贡献 stable 对象
contribute_handle(Handle) 同上,但注册表名称取自 vtable
provide_service(id, obj) / provide_service_shared(id, Arc<..>) 声明服务并放入 origin
decorate_service(id, priority, obj) 为服务添加层(装饰器)
into_batch() 用于实时 Registry 的快照

该特征的其他属性:optional_dependencies()(不传递性拉取)、always_on()(始终启用且最先;仅用于内核)、 dependencies()(形如 "admin@^2.1" 的版本要求)。

32.3 ABI 契约

跨越边界的接口(stable 对象注册表、服务) 由带 #[abi_trait] 的特征描述:

use agent_os_abi::abi_trait;

#[abi_trait(id = "DocSource", name = "documentation", version = 1)]
pub trait DocSource {
    fn manifest(&self) -> Result<String, String>;
    fn markdown(&self, id: &str, lang: &str) -> Result<String, String>;
}

语法受限(否则编译错误):

#[abi_struct] 标记跨越 ABI 的 #[repr(C)] 结构体:

use agent_os_abi::abi_struct;

#[abi_struct]
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ItemRow { pub id: i64, pub score: f64, pub name: agent_os_abi::AStr }

该宏生成带 VtableHeader { trait_id, name, abi_version, size, destroy }, Copy-handle, owning-обёртку Owned<T> 的 #[repr(C)] vtable、 Borrowed<T>、dispatch-impl 以及 producer 擦除(into_owned()/into_object())。 handle 可以存放在 OwnedObject 中并通过边界传递。

导出对象的模块(用于 DLL)声明其工厂:

use agent_os_abi::abi_registry;
abi_registry! {
    BuiltinSource as DocSource;
}

abi_registry! 生成 abi_objects() -> Vec<Handle>(用于 linked)和符号 ao_module_registry(用于 DLL)。

工具模块也以 stable 对象 agent_os.ToolEngine (agent_os_tool_api)形式提供到注册表 tool.engines;宿主桥将每个对象 转换为 YAML 工具工厂。ToolEngine —— ABI v2:kind、describe(schema)和 execute,外加带 回调对象 agent_os.ToolProgress 的 execute_ctx(context_json, config, args, progress)。结果可以以「信封」 { content, success, data, visual, cost } 返回。使用 sensitive 端口 (例如 agent_os.Fs/agent_os.Process)的模块需在 capabilities 中声明它们 (declare_module!(…, capabilities = ["agent_os.Fs"]))。带工具的 dual-transport 模块示例 —— agent_os_code(见 模块 code)。

32.4 HTTP 路由

路由是注册表 routes 中的一个 stable 对象 agent_os_web::Route:

use agent_os_web::{Route, RouteMeta, HttpRequest, HttpResponse, ACCESS_ADMIN};

struct ListAgents;

impl Route for ListAgents {
    fn meta(&self) -> Result<RouteMeta, String> {
        Ok(RouteMeta::new("GET", "/v1/analytics/agents", ACCESS_ADMIN))
    }
    fn handle(&self, _req: HttpRequest) -> Result<HttpResponse, String> {
        Ok(HttpResponse::json(200, &serde_json::json!({ "agents": [] })))
    }
}

Access —— public / user / admin;宿主以所需的 gate 挂载路由(require_admin / require_user),而未知级别不挂载 (fail closed)。对于典型场景有现成的构造函数: contribute_get、contribute_route、contribute_asset(静态资源)、 respond_cached/weak_etag(ETag)、mime_for。注册表的所有者是 always-on 模块 agent_os_web::WebModule;可以在它之前贡献(元素 会被推迟)。

32.5 服务与 host 端口

领域服务(模块 ↔ 模块)是形如 Service 的注册表,恰好有一个 origin 和 N 个层:

// provider (origin)
b.provide_service("dbsvc", MyDb::new().into_owned().into_object());
// декоратор (слой), priority ниже — ближе к origin
b.decorate_service("dbsvc", 0, MetricsDb::new().into_owned().into_object());

// потребитель: requires: ["db-provider"], затем
fn bind(&self, ctx: &BindContext) {
    if let Some(obj) = ctx.service("dbsvc") {
        self.db.set(SharedDbService::from_object(obj).expect("interface mismatch"));
    }
}

Host 端口(内核 → 模块)可通过 agent_os_sdk::service(id) + agent_os_host_api::client::* 以传输中立方式访问:

if let Some(svc) = agent_os_sdk::service(agent_os_host_api::ids::STATE) {
    if let Some(state) = agent_os_host_api::client::state(svc.handle()) {
        let _ = state.set("analytics", "last_run", "2026-01-01");
    }
}

主要端口:Log、Config、State、Metrics、System、Agents、 Tools、Llm、Script、Http、Secrets、Fs、Process、Messages、 Events、Sessions、Tasks(+ Job)、RequestContext、Extension。完整 列表 —— agent_os_host_api::ids。

后台任务不运行自己的运行时:模块提供同步的 Job::step (JSON { "continue": bool, "next_ms": u64 }),而宿主在自己的 运行时上、在模块的 capability 作用域下执行各步骤:

let tasks = agent_os_host_api::client::tasks(svc.handle()).unwrap();
let handle = tasks.spawn("analytics", my_job.into_owned())?;  // my_job: impl Job
// …
tasks.cancel(&handle)?;

Capabilities。 敏感端口(Secrets、Http、Messages、 Sessions、control-plane 等)fail-closed:模块只有在 capabilities() / module.yaml → capabilities 中声明了 id 才能看到它们。宿主设置 每个模块代码入口(路由处理器、 Job::step、事件回调)周围的 capability 作用域。

32.6 module.yaml 描述符

对 linked 和 DLL 模块统一。Rust 模块从 Module 推导出它;DLL 可以 读取文件或发送 register_module_descriptor。

id: analytics
version: 1.2.0
abi: 8                              # требуемая версия ABI для libs
requires: [auth, "admin@^2.1"]      # граф: топосорт + авто-включение
optional: ["gpu@^1"]                # только если цель уже включена
capabilities: [agent_os.Secrets]    # чувствительные порты (fail-closed)
libs:
  linux:   lib/libanalytics.so
  windows: lib/analytics.dll
  macos:   lib/libanalytics.dylib
bundle:
  root: config
  points: [agents, hooks, tools, routes, workflows]
install:
  verify: sha256
  code: true
  artifacts:
    - id: runtime
      dest: runtime
      platforms:
        linux:   { url: "https://…/rt.tgz", sha256: "…", extract: tar.gz }
        windows: { url: "https://…/rt.zip", sha256: "…", extract: zip }
provides: [analytics.panel]         # свои слоты/сервисы
contributes: [admin.nav]            # в чужие слоты

启用 — 在 config/server.yaml 中:

modules:
  enabled: [auth, admin, docs, distrib, analytics]
  auto_install: false

enabled ≠ installed:列表是「启动时激活什么」;安装工件 (网络)是单独的操作。requires 会传递性地触达;未知 id — warning;模块缺少 bundle/DLL 或 ABI 不匹配 — 模块加载 错误,系统其余部分照常启动。

32.7 两种传输与打包

agent_os_sdk::declare_module! 从同一个函数生成两种传输:

fn contribute(h: &mut dyn RegistryHost) {
    h.data_registry("analytics.items", "{}");
    h.data_item("analytics.items", r#"{"title":"x"}"#);
}
agent_os_sdk::declare_module!(AnalyticsModule, "analytics", contribute);

Cargo:

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["host"]
host = ["agent_os_sdk/host", "dep:agent_os_modules", "dep:serde", "dep:serde_json"]

[dependencies]
agent_os_sdk = { path = "…/agent_os_sdk" }
agent_os_abi = { path = "…/agent_os_abi" }
agent_os_modules = { path = "…/agent_os_modules", optional = true }
serde_json = { version = "1", optional = true }

宿主以相同方式加载两者 — 通过 ModulePackage:

// internal Rust-модуль
let pkg = agent_os_modules::ModulePackage::internal(AnalyticsModule)
    .with_bundle(files);

// DLL: package собирается из register_module_descriptor + register_bundle_file + регистраций
let pkg = agent_os_modules_runtime::plugin_package("analytics").unwrap();

let rt = ModuleRuntime::build_packages_with_host(
    packages, &enabled, "data/modules", host_services,
)?;
rt.activate()?;

bundle 的物化 — edit-preserving,在 data/builtin/<id>/ 中带 version-manifest(.manifest.json)。

32.8 Lifecycle

use agent_os_modules::{ModuleLifecycle, InstallContext, InstallReport,
                       UninstallContext, RuntimeContext};

impl ModuleLifecycle for AnalyticsLifecycle {
    fn install(&self, ctx: &mut InstallContext) -> Result<InstallReport, String> {
        // скачать/материализовать артефакты в ctx.install_dir
        Ok(InstallReport::default())
    }
    fn activate(&self, _rt: &mut RuntimeContext) -> Result<(), String> { Ok(()) }
    fn deactivate(&self, _rt: &mut RuntimeContext) -> Result<(), String> { Ok(()) }
    fn uninstall(&self, _ctx: &mut UninstallContext) -> Result<(), String> { Ok(()) }
}

所有方法默认都是 no-op。运行时按拓扑顺序激活模块, 并按相反顺序停用;状态 — data/modules/<id>/state.json (version、status、工件)。激活失败会将模块标记为 failed,但 系统照常启动。DLL 不会被卸载;deactivate — graceful stop。

32.9 完整示例

典型的 dual-transport 模块 — 就是这份参考手册(agent_os_docs):

// crates/modules/surfaces/agent_os_docs/src/interface.rs
#[abi_trait(id = "DocSource", name = "documentation", version = 1)]
pub trait DocSource {
    fn manifest(&self) -> Result<String, String>;
    fn markdown(&self, id: &str, lang: &str) -> Result<String, String>;
}

// crates/modules/surfaces/agent_os_docs/src/lib.rs
fn contribute(h: &mut dyn RegistryHost) {
    h.stable_registry(DOCS_REGISTRY, DOC_SOURCE_ID);
}
agent_os_sdk::declare_module!(DocsModule, "docs", contribute, abi_objects);

abi_registry! {
    BuiltinSource as DocSource;
}

最小的 cdylib 路由引擎 — crates/modules/fixtures/agent_os_route_demo (纯 impl,没有 unsafe/thunk)。更多示例:agent_os_widget (dual-transport 表面)、agent_os_test_plugin(raw C ABI,所有类别)、 agent_os_test_host_client(从 cdylib 消费 host 服务)。

32.10 构建与测试

cargo build -p <module>                      # linked (feature host, по умолчанию)
cargo build -p <module> --no-default-features # lean DLL

./scripts/build-module-dlls.sh               # Windows: scripts\build-module-dlls.ps1
./scripts/test-module-system.sh              # stage DLLs + все module-system тесты

Lean 库会被暂存到单独目录 target/modules/<profile>/ — 否则 linked 构建(包括 cargo build --workspace)会用 没有导出的空占位符覆盖真正的 DLL。加载 DLL 的测试 (agent_os_plugin_host、agent_os_modules_runtime、agent_os_route_abi) 通过 agent_os_test_support::require_dll 获取工件,如果不存在则失败(而不是 skip)。

传输对等性由 agent_os_modules_runtime/tests/transport_parity.rs 固定:linked 和 DLL 必须给出 相同的注册表/路由目录。

32.11 接下来去哪