带作用域的设置与待处理配置
设置是必须在部署中给定,模块、代理、工具或钩子才能工作的值:API 密钥、账户名、endpoint。设置由其所有者声明;它们存储在数据库中,按需投影到进程环境变量,并通过管理后台中的非阻塞对话向用户收集。
这取代了以往的做法,即每个消费者自行发明自己的 *_env 解析,而唯一的逐步设置是 LLM 的首次启动向导(参见覆盖层界面)。引导流程保持不变:它只是针对 LLM 的阻塞式首次启动向导。设置则是管理后台内部通用、始终可用且非阻塞的机制。
概念
| 术语 | 含义 |
|---|---|
声明(SettingSpec) |
所有者需要什么:id、标题、kind、scope、是否必需、UI 提示。 |
| 键 | 规范 id {owner}/{id}(例如 github/token、agent:greeter/greeting)。 |
| 所有者 | 声明该设置的模块或代理模板。 |
| 作用域 | 值归属于谁(见下文)。 |
| 值 | 为元组 (key, scope, scope_id) 保存的字符串。 |
| 待处理(pending) | 在所需作用域中没有值的必需设置,或模块的运行时请求。 |
作用域与解析
值存在于四个层级,从通用到具体:
| 作用域 | 归属于 | 谁可以修改 |
|---|---|---|
global |
整个部署 | 具有完整作用域的管理员 |
principal |
管理员/操作员的身份(Principal.name) |
该主体 / 完整管理员 |
agent |
代理模板 | 被授予该模板权限的管理员 |
local_user |
规范终端用户(来自 widget/登录的 id) | 用户本人(自助服务) |
消费者从最具体到最通用解析设置:
local_user → principal → agent → global → 声明的 env 变量 → 编辑后的配置 → default
scope 声明是支持的最具体层级;更通用的层级用作回退。解析与状态存储的 get_resolved 一致(user → session → agent)。
示例:github/token 由 scope: global 声明(每个部署一个键)。
mycrm/password 由 scope: local_user 声明——每个用户设置自己的值,如果未设置——则使用全局值(如果有)。
声明设置
Rust 模块重写 Module::settings:
fn settings(&self) -> Vec<agent_os_modules::SettingSpec> {
vec![agent_os_modules::SettingSpec {
id: "token".into(),
title: "GitHub token".into(),
description: "Personal access token with `repo` scope.".into(),
kind: SettingKind::Secret,
scope: SettingScope::Global,
required: true,
..Default::default()
}]
}
DLL 模块在 module.yaml 中声明相同的形式:
id: github
version: 1.0.0
settings:
- id: token
title: GitHub token
kind: secret
scope: global
required: true
env: GITHUB_TOKEN
trigger: on_login
字段
| 字段 | 值 | 含义 |
|---|---|---|
id |
string | 在所有者内部唯一;键为 {owner}/{id}。 |
title / description |
string | 对话中的标签;description 为 markdown。 |
kind |
string secret bool int url select |
控件 + 校验。 |
scope |
global principal agent local_user |
支持的最具体层级。 |
required |
bool | 影响 pending 的计数。 |
default |
string | 未设置时显示的值(不存储)。 |
options |
list | 用于 kind: select。 |
env |
string | 值投影到的进程变量名。 |
trigger |
on_login on_use manual |
对话何时显示它。 |
enforce |
advisory block |
block:消费者在未设置前会以结构性错误失败。 |
verify |
object | 可选的凭据检查(见下文)。 |
没有 Module 的模块(declare_module!、DLL)
通过 agent_os_sdk::declare_module! 构建的模块(dual-transport 形式,用于 searchers 等便携/DLL 模块),不实现 Module::settings()。它通过向已知的 agent_os_sdk::SETTINGS_DECLARATIONS 注册表("settings.declarations")贡献 data-item 来声明设置,该注册表由 always-on kernel 拥有:
fn contribute(h: &mut dyn agent_os_sdk::RegistryHost) {
h.stable(agent_os_tool_api::TOOL_ENGINES, /* … */);
h.data_item(
agent_os_sdk::SETTINGS_DECLARATIONS,
&serde_json::json!({
"owner": "searchers",
"spec": {
"id": "firecrawl_api_key",
"kind": "secret",
"scope": "global",
"required": true,
"env": "FIRECRAWL_API_KEY",
"trigger": "on_login"
}
})
.to_string(),
);
}
这是传输中立的:同一个 contribute 在 linked 和 DLL 中都能工作。宿主将这些 items 与来自 Module::settings() 的任何 items 合并。
存储
值存储在单独的 DuckDB 中,logging.settings_db_path(默认 logs/settings.db):
CREATE TABLE settings (
owner TEXT NOT NULL,
setting_id TEXT NOT NULL,
scope_kind TEXT NOT NULL, -- global | principal | agent | local_user
scope_id TEXT NOT NULL, -- "" для global
value TEXT NOT NULL,
secret BOOLEAN NOT NULL,
updated_at TIMESTAMP NOT NULL,
updated_by TEXT NOT NULL,
PRIMARY KEY (owner, setting_id, scope_kind, scope_id)
);
- 数据库是事实来源。
secret值使用连接器密钥加密(connector_encryption_key_env);API 从不返回它们,只返回掩码状态。- 每条记录都会追加到
logs/settings_audit.jsonl。
投影到环境
当声明指定 env: NAME 时,值会在启动时以及每次写入时投影到运行进程的环境(通过现有的 agent_os.Env 机制)。因此,带有 *_env 的现有工具和 env_var(name) 脚本会立即看到新值——无需重启。
优先级(从通用到具体):手动值 .env / config/env.overrides.yaml 优先于来自设置的投影(操作员的应急出口)。在管理后台中,此类设置会标记为“被外部覆盖”。
强制执行与检查
enforce: advisory(默认)——对话仅作提示;消费者使用默认值/缺失值工作。enforce: block——消费者在设置未给定时会返回结构性错误“需要设置”。调用方(聊天、CLI、widget)会将其转换为请求,因此设置会在使用点被请求。verify是可选的,并按每个设置进行配置:
verify:
kind: http # none | http | script
method: GET
url: "https://api.example.com/me"
headers: { Authorization: "Bearer {value}" }
expect_status: 200
{value} 在检查时被替换。结果写入
verified_at / verify_error 并在对话框中显示。没有 verify 时,值
不经校验直接保存。
端口与 HTTP API
模块通过宿主端口 agent_os.Settings(sensitive,
capability agent_os.Settings)访问设置:
declarations() -> 所有规格(永远不是值)
status(scope, scope_id) -> 声明 + resolved/pending
get(key, scope, scope_id) -> 已解析的值(未设置时为 "")
set(key, value, scope, id, actor)
request(spec_json) -> 发起运行时请求
HTTP 表面由功能模块 settings 拥有:
| 路由 | 访问权限 | 用途 |
|---|---|---|
GET /v1/settings |
admin | 声明 + 调用方状态(按 auth 作用域过滤) |
GET /v1/settings/pending |
admin | 未设置的必填项 + 运行时请求 |
POST /v1/settings |
admin | { key, value, scope, scope_id } |
POST /v1/settings/request |
admin | 发起运行时请求 |
GET/POST /v1/settings/me |
user | 最终用户自助服务(local_user) |
管理界面
模块 settings 提供 /settings 和 /ui/settings.js,并在管理菜单中添加
设置 项。成功登录后,登录页面轮询 /v1/settings/pending 并显示一个
可关闭的对话框;关闭状态会记住到浏览器会话,下次登录时提示会再次出现。
导航不受任何阻塞:/settings 也可直接访问。
设置的相关性
页面和对话框优先显示实际使用的内容:
- 模块相关性。 如果没有任何已配置的 agent
(
config/agents/**)引用该模块工具的任何kind(例如,没有人使用google_tool),则该模块的设置 被标记为relevant: false。它们仍然保留在/settings的折叠 区段“未被任何 agent 使用”中,但登录后不会显示在对话框里。 已填写的设置(数据库中的值或env)会使模块保持 相关,因此已配置的集成始终可见。 - 附加项(按设置)。 可选的(
required: false) 无值设置被标记为advanced: true并移入折叠 区段“附加项”。一旦设置了值,它就会返回 主列表。
GET /v1/settings 返回两个标志;GET /v1/settings/pending 按
relevant != false 过滤,因此未使用的集成不会在登录时“烦人”。
kind → модуль 的对应关系取自 tool.engines 注册表;使用的
工具类型——来自 agent 的 tool 配置。
示例:GitHub 令牌
- 模块
github声明github/token(secret、global、required、env: GITHUB_TOKEN、trigger: on_login)。 - 启动时宿主看到无值的声明 → pending。
- 管理员登录;外壳显示对话框。
- 值保存到
settings.db,投影到进程环境中的GITHUB_TOKEN, 并写入审计。 - 带有
token_env: GITHUB_TOKEN的工具从下一次调用起即可工作,无需 重启。 - 下次登录不再显示提示。
首批使用方
模块 google 和 yandex 声明 oauth_client_id / oauth_client_secret
(全局密钥,投影到 GOOGLE_OAUTH_CLIENT_SECRET /
YANDEX_OAUTH_CLIENT_SECRET)。工具配置已经读取这些变量,
因此在 .env 中设置了它们的部署不会看到提示;全新部署
会在管理员登录时询问一次。由于它们从环境解析,在
已设置密钥时不会烦人。
模块 searchers 声明 firecrawl_api_key(通过上面的注册表通道——
它是宏生成的),投影到 FIRECRAWL_API_KEY,带有
trigger: on_login —— 在登录后的对话框和设置页面中可见,并且
值会投影到进程而无需重启。
实现阶段
- Global —— 模型、注册表、数据库、
/v1/settings*、管理对话框和页面。 - Principal 和 Agent 作用域,按 auth 作用域过滤。
- Local_user 作用域(加密)和小组件中的自助服务。
- 运行时请求、
enforce/verify、select、由 agent 声明的设置。
与引导流程的关系
引导流程(覆盖表面)—— 仅针对 LLM 提供方的阻塞式 首次运行向导。设置是管理界面内一个独立的、非阻塞的、 通用机制。它们不会覆盖路由,也不会阻断 操作员对配置的访问。