Настройки со скоупами и ожидающая конфигурация
Настройка — это значение, которое должно быть задано в деплое, чтобы модуль, агент, инструмент или хук заработал: 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 |
каноническому конечному пользователю (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 форма,
используется портативными/DLL-модулями вроде searchers), не реализует
Module::settings(). Он объявляет настройки, контрибутя data-item в
известный реестр agent_os_sdk::SETTINGS_DECLARATIONS
("settings.declarations"), которым владеет 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().
Хранение
Значения лежат в отдельной 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 побеждает проекцию из настроек (аварийный выход
оператора). В админке такая настройка помечается как «перекрыто извне».
Enforcement и проверка
enforce: advisory(по умолчанию) — диалог лишь подсказывает; потребители работают с дефолтом/отсутствующим значением.enforce: block— потребитель при незаданной настройке возвращает структурную ошибку «нужна настройка». Вызывающие (чат, CLI, виджет) превращают её в запрос, так что настройка запрашивается в точке использования.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) |
Админ-UI
Модуль settings отдаёт /settings и /ui/settings.js и добавляет пункт
Настройки в меню админки. После успешного входа страница логина
опрашивает /v1/settings/pending и показывает закрываемый диалог; закрытие
запоминается на сессию браузера, и подсказка возвращается при следующем входе.
Навигацию ничего не блокирует: /settings доступна и напрямую.
Релевантность настроек
Страница и диалог показывают приоритетно то, что реально используется:
- Модульная релевантность. Если ни один сконфигурированный агент
(
config/agents/**) не ссылается ни на одинkindинструмента модуля (например, никто не используетgoogle_tool), настройки этого модуля помечаютсяrelevant: false. Они остаются на/settingsв свёрнутой секции «Не используется ни одним агентом», но не показываются в диалоге после входа. Заполненная настройка (значение в БД илиenv) держит модуль релевантным, поэтому настроенная интеграция всегда видна. - Дополнительно (по настройке). Необязательная (
required: false) настройка без значения помечаетсяadvanced: trueи уезжает в свёрнутую секцию «Дополнительно». Как только значение задано, она возвращается в основной список.
GET /v1/settings возвращает оба флага; GET /v1/settings/pending фильтрует по
relevant != false, поэтому неиспользуемые интеграции не «надоедают» при входе.
Соответствие kind → модуль берётся из реестра tool.engines; используемые
виды инструментов — из 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, настройки, объявляемые агентами.
Связь с онбордингом
Онбординг (Поверхности-оверлеи) — блокирующий мастер первого запуска только для провайдера LLM. Настройки — отдельный, неблокирующий, общий механизм внутри админки. Они не перекрывают маршруты и не могут закрыть оператору доступ к конфигурации.