Files
Agentsd/docs/02-插件模型.md
未知时光 e75cfc6812 Fix documentation inconsistencies found by code audit
- docs/01: sync all syscall signatures with proto (missing params,
  invented callback params, stream semantics, return types)
- docs/02: mark unimplemented features (FFI system plugins, topo sort,
  lifecycle CLI, watchdog); clarify plugin.yaml is not parsed by the
  kernel - registration goes through gRPC PluginBridge.Register;
  correct permission model and session_id descriptions
- docs/03: check off implemented stage 3 items (Hermes adapter,
  Claude Code plugin)
- docs/04: data/ actual location, add Cargo.toml to plugin trees,
  note __init__.py is hand-maintained
- README: status line now reflects actual progress

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:59:54 +08:00

7.3 KiB

02 - 插件模型

三种插件(对标 Linux 三层)

┌─────────────────────────────────────────────────────┐
│  标准插件 ← 应用服务(nginx、docker)                  │
│  可依赖基础插件 + 其他标准插件                        │
├─────────────────────────────────────────────────────┤
│  基础插件 ← 基础 daemon(udevd、resolved)            │
│  只依赖 9 个 syscall,无插件依赖                      │
├─────────────────────────────────────────────────────┤
│  系统插件 ← 内核模块 / 设备驱动(.ko)                │
│  注册为 syscall 的实现                               │
├─────────────────────────────────────────────────────┤
│  agentsd ← Linux 内核 + systemd(PID 1)             │
└─────────────────────────────────────────────────────┘

系统插件(= 内核模块 / 驱动) 设计目标,未实现

为 syscall 提供具体实现。类比 insmod alsa_driver.ko

# 设计示意(provides 字段与 FFI 加载尚未实现)
id: alsa-audio
type: system
provides: audio
entry: ./libalsa_audio.so
  • FFI 加载(.so/.dylib/.dll),零开销 —— 未实现,当前无动态库加载代码
  • 只限 Rust / C ABI
  • 同一 syscall 可有多个驱动,同时只激活一个
  • 无依赖
  • 当前内核仅拒绝外部进程注册 type: system(防止权限提升),加载机制本身待实现

基础插件(= systemd 基础 unit,无依赖)

只调 syscall,不调其他插件。类比 systemd-resolved.service(只用内核网络栈)。

id: tts-edge
name: Edge TTS
version: 0.1.0
type: basic
syscalls:
  - audio.play
  - network.request
exports:
  - name: speak
    description: Text to speech
entry: python tts_plugin.py
  • 独立进程,gRPC/TCP 通信(后续切 UDS/Named Pipe)
  • 零插件依赖,加载顺序无所谓(= 无 After/Requires)
  • 任意语言

标准插件(= systemd 应用 unit,有依赖)

组合其他插件。类比 nginx.service(After=network.target, Requires=...)。

id: voice-agent
name: Voice Agent
version: 0.1.0
type: standard
syscalls:
  - input.text
  - display.text
depends:
  - tts-edge
  - stt-whisper
  - chat-agent
exports:
  - name: voice_chat
    description: Voice conversation loop
entry: cargo run -p voice-agent
  • 可依赖基础插件 + 其他标准插件
  • 按依赖拓扑排序加载(= systemd ordering)—— 未实现:depends 当前仅随注册存储,不做解析/排序/校验(阶段 4)
  • 任意语言

plugin.yaml 的当前角色:内核不会扫描或解析 plugin.yaml——它目前是给开发者看的清单(entry/port 供手动启动用)。 实际注册机制是插件进程启动后主动调用 PluginBridge.Register(gRPC),传 protobuf PluginInfo{id, name, version, plugin_type, syscalls, depends, exports, endpoint}。 基于 YAML 的自动发现/自动加载属于阶段 4 的插件管理目标。


通信:gRPC(= D-Bus)

类比 Linux 的 D-Bus 进程间通信总线,agentsd 用 gRPC。

为什么 gRPC

  • protobuf 二进制,快
  • 流式 RPC(音频流、文件 watch)
  • 所有语言都有库
  • .proto = 接口契约 + 代码生成

传输分层

插件类型 传输 延迟 Linux 类比
系统插件(未实现) FFI ~纳秒 内核态函数调用
基础/标准(本机,当前) gRPC/TCP ~毫秒 D-Bus over TCP
基础/标准(目标) gRPC/Unix socket / Windows Named Pipe ~微秒 D-Bus over UDS
远程(可选) gRPC/TCP ~毫秒 D-Bus over TCP

proto 示例

syntax = "proto3";

service FS {
  rpc Read(ReadRequest) returns (ReadResponse);
  rpc Write(WriteRequest) returns (WriteResponse);
  rpc Delete(DeleteRequest) returns (DeleteResponse);
  rpc List(ListRequest) returns (ListResponse);
  rpc Watch(WatchRequest) returns (stream WatchEvent);
}

service Audio {
  rpc Play(stream AudioChunk) returns (PlayResponse);
  rpc Record(RecordRequest) returns (stream AudioChunk);
}

插件间调用(经内核路由)

service PluginBridge {
  rpc Call(CallRequest) returns (CallResponse);
  rpc StreamCall(CallRequest) returns (stream CallResponse);
}

message CallRequest {
  string target = 1;
  string fn = 2;
  bytes args = 3;
}

生命周期管理(= systemctl) 阶段 4 目标,未实现

以下命令为规划中的 CLI,当前内核仅提供 gRPC server,无任何子命令:

agentsd 命令 systemd 对标 作用
agentsd start <id> systemctl start 启动插件
agentsd stop <id> systemctl stop 停止插件
agentsd restart <id> systemctl restart 重启
agentsd reload <id> systemctl reload 热更新
agentsd status systemctl status 状态查询
agentsd list systemctl list-units 列出所有插件
agentsd logs <id> journalctl -u 查日志
agentsd enable <id> systemctl enable 开机自启
agentsd disable <id> systemctl disable 取消自启

加载顺序(= systemd boot) 设计目标,未实现

当前插件由开发者手动启动,启动后自行注册。规划中的加载顺序:

1. 系统插件(FFI,注册 syscall 实现)    ← 类比内核模块加载
2. 基础插件(gRPC,任意顺序)            ← 类比 basic.target
3. 标准插件(gRPC,拓扑排序)            ← 类比 multi-user.target

看门狗(= Restart=always) 阶段 4 目标,未实现

插件崩溃 → agentsd 自动重启(可配置策略):

  • always:总是重启
  • on-failure:异常退出才重启
  • never:不重启

权限(= seccomp + cgroup)

  • 系统插件:内核信任,无限制
  • 基础/标准插件:按 syscalls 白名单精确到方法(如 fs.list)校验,也支持 fs / fs.* / * 授权(= seccomp filter)
  • depends 字段当前仅声明、随注册存储,不参与权限校验(依赖校验属阶段 4)

身份认证

所有 syscall 调用必须携带 x-plugin-id gRPC metadata,否则返回 UNAUTHENTICATED。 这是第一道安全边界——即使 server 监听 TCP,未注册的进程也无法调用任何 syscall。

认证流程:

  1. 插件通过 PluginBridge.Register 注册(返回的 session_id 目前仅作回执,不参与后续校验)
  2. 后续所有 syscall 请求须在 gRPC metadata 中带 x-plugin-id: <plugin_id>
  3. 内核校验:plugin_id 存在 → status=Running → syscall 在该插件的白名单内 → 放行

stream_call 身份绑定

PluginBridge.StreamCall 用于订阅 Timer 等回调事件。安全策略:

  • 必须携带 x-plugin-id(否则 UNAUTHENTICATED)
  • 只能订阅自己的事件通道(target 为空或等于自身 plugin_id)
  • 尝试订阅其他插件的事件返回 PERMISSION_DENIED

死插件路由保护

PluginBridge.Call 在路由到目标插件前,校验其 status 为 Running。 Stopped/Failed 的插件会立即返回错误,而不是等 5 秒连接超时。