带作用域的设置与待处理配置

设置是必须在部署中给定,模块、代理、工具或钩子才能工作的值: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)
);

投影到环境

当声明指定 env: NAME 时,值会在启动时以及每次写入时投影到运行进程的环境(通过现有的 agent_os.Env 机制)。因此,带有 *_env 的现有工具和 env_var(name) 脚本会立即看到新值——无需重启。

优先级(从通用到具体):手动值 .env / config/env.overrides.yaml 优先于来自设置的投影(操作员的应急出口)。在管理后台中,此类设置会标记为“被外部覆盖”。

强制执行与检查

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 也可直接访问。

设置的相关性

页面和对话框优先显示实际使用的内容:

GET /v1/settings 返回两个标志;GET /v1/settings/pending 按 relevant != false 过滤,因此未使用的集成不会在登录时“烦人”。 kind → модуль 的对应关系取自 tool.engines 注册表;使用的 工具类型——来自 agent 的 tool 配置。

示例:GitHub 令牌

  1. 模块 github 声明 github/token(secret、global、required、 env: GITHUB_TOKEN、trigger: on_login)。
  2. 启动时宿主看到无值的声明 → pending。
  3. 管理员登录;外壳显示对话框。
  4. 值保存到 settings.db,投影到进程环境中的 GITHUB_TOKEN, 并写入审计。
  5. 带有 token_env: GITHUB_TOKEN 的工具从下一次调用起即可工作,无需 重启。
  6. 下次登录不再显示提示。

首批使用方

模块 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 —— 在登录后的对话框和设置页面中可见,并且 值会投影到进程而无需重启。

实现阶段

  1. Global —— 模型、注册表、数据库、/v1/settings*、管理对话框和页面。
  2. Principal 和 Agent 作用域,按 auth 作用域过滤。
  3. Local_user 作用域(加密)和小组件中的自助服务。
  4. 运行时请求、enforce/verify、select、由 agent 声明的设置。

与引导流程的关系

引导流程(覆盖表面)—— 仅针对 LLM 提供方的阻塞式 首次运行向导。设置是管理界面内一个独立的、非阻塞的、 通用机制。它们不会覆盖路由,也不会阻断 操作员对配置的访问。