- 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>
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),传 protobufPluginInfo{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。
认证流程:
- 插件通过
PluginBridge.Register注册(返回的session_id目前仅作回执,不参与后续校验) - 后续所有 syscall 请求须在 gRPC metadata 中带
x-plugin-id: <plugin_id> - 内核校验: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 秒连接超时。