Server configuration

🔵 The platform is configured by two files: config/server.yaml (core, provider, logging, auth, widget) and config/budgets.yaml (budget groups and template binding). Schedules are in config/cron.yaml, change monitoring is in config/monitors.yaml, workflows are in config/workflows/. Secrets are in .env (machine-local) or encrypted in config/secrets.yaml (see Config versioning).


3.1 Config source — local folder or git repository

The entire config tree (server.yaml, agents/, web/, workflows/, budgets.yaml, cron.yaml, monitors.yaml) is loaded from a single root. By default this is a local ./config; instead, a git repository can be cloned so that the config is versioned and reviewed separately from the binary.

Priority: CLI flag → environment variable → local ./config.

CLI flag Variable Default value What it does
--config-repo <url> AGENT_OS_CONFIG_REPO — clone config from git (otherwise — local folder)
--config-ref <ref> AGENT_OS_CONFIG_REF main branch / tag / commit to which the config is pinned
--config-dir <path> AGENT_OS_CONFIG_DIR ./config local config folder (ignored in git mode)
--config-poll <secs> AGENT_OS_CONFIG_POLL off periodically git pull + hot-reload config
AGENT_OS_CONFIG_REPO=https://github.com/org/agent-config \
AGENT_OS_CONFIG_REF=v1.2.0 \
./agent-os serve

The repository is cloned into ~/.agent-os/configs/<repo>-<ref> onto a local branch (--config-ref), not into detached HEAD, so that the host can commit and push; the commit SHA is written to the log at startup and is visible on /health in the config field. The config poller does merge --ff-only/rebase — local commits (config versions) are not overwritten. Secrets can be stored in the config repository encrypted (config/secrets.yaml, see Config versioning); .env and env.overrides.yaml remain outside the repository.


3.2 config/server.yaml

provider — LLM

provider:
  base_url: "https://api.deepseek.com"   # любой OpenAI-совместимый endpoint
  model: "deepseek-v4-flash"
  api_key: ""                            # опционально; предпочтительно .env
  pricing:                               # оценка стоимости инференса
    prompt_per_million: 0.14
    cached_per_million: 0.014
    completion_per_million: 0.28
  fallbacks: ["strong"]                  # цепочка failover при сбое провайдера
  circuit_breaker:                       # пропускать упавший провайдер
    failure_threshold: 5                 # подряд ошибок до размыкания (0 = выкл.)
    cooldown_secs: 30                    # пауза до пробного запроса
    half_open_max: 1                     # успешных проб для замыкания
Field Default Description
base_url https://api.deepseek.com base URL of the OpenAI-compatible provider
model deepseek-v4-flash model name
api_key empty key; empty value → taken from env
pricing.prompt_per_million 0.14 $ per 1M prompt tokens
pricing.cached_per_million 0.014 $ per 1M cached prompt tokens
pricing.completion_per_million 0.28 $ per 1M completion tokens
fallbacks [] names of models: for failover (in order)
circuit_breaker.failure_threshold 5 consecutive errors before opening; 0 = disable
circuit_breaker.cooldown_secs 30 pause before the probe request (half-open)
circuit_breaker.half_open_max 1 consecutive successful probes to close

Circuit breaker (native). After failure_threshold consecutive errors the provider is marked "open" and is skipped in the failover chain — requests go to the next provider instead of hammering the failed one. After cooldown_secs the provider is probed with a single request (half-open): success closes the circuit, an error opens it again. The breaker key is the model-config name (default, strong, …). The mechanics are part of the core (agent_os_core::circuit_breaker). Here a global default is set; a specific agent can override it with the kind: circuit_breaker hook (see 06-hooks-routing §6.17). It is reset by the operator via Kernel::provider_breakers().reset(name).

Key selection order: provider.api_key → AGENT_OS_API_KEY → DEEPSEEK_API_KEY. Empty everywhere → agents respond with simulated answers.

models — named models

Additional model configs: each becomes a separate provider with its own base_url / api_key / model / pricing. An agent template selects the desired one via the model: field in its agent.yaml. provider: above is the default (it is also registered as "default").

models:
  cheap:
    base_url: "https://api.deepseek.com"
    model: "deepseek-chat"
  strong:
    base_url: "https://api.openai.com/v1"
    model: "gpt-4o"
    api_key: ""

kernel — scheduler and sessions

kernel:
  scheduler: round_robin
  concurrency_limit: 3
  time_slice_secs: 30
  live_reload: true
  session_ttl_secs: 300
  session_cleanup_interval_secs: 60
  max_output_tokens: 4096
Field Default Description
scheduler round_robin scheduling policy: round_robin / priority_preemptive / fair_share / deadline_driven
concurrency_limit 3 maximum concurrent LLM streams
time_slice_secs 30 preemption quantum (sec) — only with priority_preemptive; 0 = off
live_reload true hot-reload config/agents/** and config/web/**
session_ttl_secs 300 maximum session idle time (sec), after which it is killed
session_cleanup_interval_secs 60 how often (sec) the session cleaner runs
max_output_tokens 4096 global cap on response length per single inference (the main lever: raises the process's per-turn budget if needed; per-agent max_output_tokens in agent.yaml overrides; 0 → default)

logging — logs and stores

logging:
  filter: "agent_os=debug"
  jsonl_dir: "logs"
  duckdb_path: "logs/events.db"
  messages_db_path: "logs/messages.db"
  messages_retention_days: 30
  state_db_path: "logs/state.db"
  temp_dir: "logs/temp"
  access_db_path: "logs/access.db"
  cron_db_path: "logs/cron.db"
  user_db_path: "logs/users.db"
  team_db_path: "logs/teams.db"
  monitor_db_path: "logs/monitors.db"
  workflow_db_path: "logs/workflows.db"
  bench_db_path: "logs/bench.db"
  side_effect_db_path: ""            # пусто = идемпотентность выключена
  outbox_db_path: ""                 # пусто = outbox/саги выключены
  dead_letter_db_path: ""            # пусто = dead-letter только в памяти
  durable_db_path: ""                # пусто = durability (durable-агенты) выключена
  pending_db_path: ""                # пусто = pending (approvals/ввод/await_event) только в памяти
Field Default Description
filter agent_os=debug logging level filter (tracing)
jsonl_dir logs JSONL logs folder (process-*.jsonl, access.jsonl)
duckdb_path logs/events.db DuckDB event store (empty string = off)
messages_db_path logs/messages.db shared message store: transcripts + FAQ cache (empty = off)
messages_retention_days 30 how many days to keep transcripts before cleanup
state_db_path logs/state.db scoped state store (session / user / agent); empty = off
temp_dir logs/temp session scratch store for generator files; file_tool reads from there (empty = off)
access_db_path logs/access.db DuckDB log of HTTP requests (empty = off)
cron_db_path logs/cron.db cron: dynamic jobs + run history (empty = dynamic jobs do not survive restart)
user_db_path logs/users.db canonical user registry: channel ids → one user id (empty = off)
team_db_path logs/teams.db command registry: membership (many-to-many) and grants; empty = commands disabled (see §45)
monitor_db_path logs/monitors.db change-monitor cursors (empty = cursors are not persisted)
workflow_db_path logs/workflows.db workflow run history (empty = off)
bench_db_path logs/bench.db benchmark history and snapshots (empty = history is not written)
side_effect_db_path (empty) side-effect idempotency store (DuckDB); empty = deduplication disabled
outbox_db_path (empty) durable outbox (DuckDB): at-least-once delivery of side effects + sagas/compensations; open intents are caught up on boot; empty = disabled
dead_letter_db_path (empty) durable dead-letter (DuckDB) for inputs quarantined by supervision poison turns; empty = in-memory only
durable_db_path (empty) durable session store (DuckDB) for agents with durable: true; empty = process persistence disabled
pending_db_path (empty) store of pending requests (approvals / input / await_event); empty = in-memory only. Needed for durable agents that survive a restart

teams — team access

Enabled by the presence of logging.team_db_path (see §45 "Teams and access"). The section selects which resources are gated by a team grant.

teams:
  gated_tools: ["gdrive_upload", "notion_write"]   # только по гранту команды
Field Default Description
gated_tools [] tools available to a user in teams only via an tool grant; empty = tool gating disabled

Access to agents and modular routes is not configured here: an agent — via an template grant (plus per-agent login), a route — via an Access::Team declaration.

sandbox — limits for processes on the host

Global policy for processes launched by tools, scripts (shell()/exec()) and the browser. Rootless limits: Unix — setrlimit (CPU/AS/NPROC/NOFILE/FSIZE) + a new session/process-group; Windows — Job Object (mem/procs/CPU, kill-on-close). FS confinement (read_roots/write_roots) — via Landlock (Linux) and path validation; tree-wide memory/CPU/pids — cgroup v2. The network is not restricted yet. Empty/enabled: false = disabled. Per-agent override — agent.yaml → sandbox:.

sandbox:
  enabled: true
  cpu_secs: 30          # лимит CPU-времени
  mem_mb: 512           # адресное пространство / память процесса
  max_procs: 64         # число процессов
  max_fds: 256          # открытых файловых дескрипторов
  max_file_mb: 64       # размер создаваемого файла (Unix RLIMIT_FSIZE)
  read_roots: []        # allowlist чтения (пусто = без ограничений); file_tool, fs_read/fs_list
  write_roots: []       # allowlist записи (пусто = без ограничений); file_tool, fs_write
  cgroup_parent: ""     # cgroup v2 для лимитов (Linux); пусто = cgroups выкл
  cpu_quota_percent: null  # доля одного ядра (cgroup cpu.max), напр. 50 = 0.5 ядра
  allow_net: true       # false = процесс в изолированном netns (Linux, без сети)
  clear_env: true       # очистить окружение
  env_allow: [PATH, LANG, HOME]   # что оставить при clear_env

Backends (Linux): FS limits (read_roots/write_roots) are applied via Landlock (rootless, kernel ≥5.13) in file_tool and script fs_*; memory/CPU/pids limits — via cgroup v2 (tree-wide, including descendants) when cgroup_parent is set; network (allow_net: false) — via unshare(CLONE_NEWUSER | CLONE_NEWNET) (no routes). On Windows, resource limits are provided by the Job Object.

Observability. Sandbox denials are counted and logged; GET /metrics exposes agentos_sandbox_violations_total{kind="…"} (e.g. fs_read_denied, fs_write_denied).

cgroup v2 — deployment requirement. cgroup_parent must be delegated to the service (rootless model): the D directory with no processes, with D/cgroup.subtree_control enabled (the library enables those available from +memory +pids +cpu), owned by the service user, and the service itself running inside D/<child>. systemd example: Delegate=yes on the unit + sandbox.cgroup_parent: /sys/fs/cgroup/<slice>/<unit>.

auth — admin API and web login

auth:
  admin_token_env: ""              # имя env-переменной с токенами (опционально)
  admin_tokens:                    # карта имя → токен (или { token, templates })
    admin: "<token>"
    drom-op:
      token: "<token>"
      templates: [drom-agent]
  users:                           # web-логин: логин → пароль (или { password | password_env, templates })
    admin:
      password_env: AGENT_OS_ADMIN_PASSWORD   # пароль из env, а не из git
    drom-viewer:
      password: "<password>"
      templates: [drom-agent]
  web_user_secret_env: ""          # env с HMAC-секретом для токенов конечных пользователей

web_user_secret_env — the name of the env variable with the HMAC secret for signing end-user identity tokens (POST /v1/user-ticket). If empty, the server generates and stores the secret itself in data/web_user_secret — identity is signed with HMAC by default. Details — in 08-http-api.

Admin endpoints accept either Authorization: Bearer <токен> or the cookie agentos_session.

The same sources (auth.users and auth.oidc) also serve end-user login in the widget: an agent may require authorization via login.required: true (providers — OIDC, allow_users — login/password from auth.users). See 04-building-an-agent and 19-widget-and-channels.

auth.oidc — login via Google / Yandex / Tinkoff

In addition to passwords (auth.users), the admin UI can log in via external identity providers (OIDC / OAuth 2.0, Authorization Code + PKCE). A role is granted by grants based on the external identity — subject (exact provider:sub), email or email_domain (suffix). The first match wins; no match = login rejected (fail-closed).

auth:
  oidc:
    base_url: ""                         # пусто = авто-вывод из запроса (Host + X-Forwarded-Proto)
    providers:
      google:
        issuer: "https://accounts.google.com"   # discovery
        client_id: "<client id>"
        client_secret_env: "GOOGLE_OIDC_CLIENT_SECRET"
      yandex:                                   # discovery нет — явные endpoints
        authorization_endpoint: "https://oauth.yandex.ru/authorize"
        token_endpoint: "https://oauth.yandex.ru/token"
        userinfo_endpoint: "https://login.yandex.ru/info?format=json"
        client_id: "<client id>"
        client_secret_env: "YANDEX_OIDC_CLIENT_SECRET"
        scopes: ["login:email", "login:info"]
        userinfo_sub_field: "id"
        userinfo_email_field: "default_email"
      tinkoff:
        issuer: "https://id.tinkoff.ru/auth/realms/tinkoff"
        client_id: "<client id>"
        client_secret_env: "TINKOFF_OIDC_CLIENT_SECRET"
    grants:
      - email_domain: "example.com"
        role: "admin"                        # полный доступ
      - email: "ops@example.com"
        templates: [drom-agent]              # оператор: только эти шаблоны

connectors — access to external APIs on behalf of the user

The agent can act in third-party services (Google Drive, Yandex.Disk, etc.) on behalf of the end user. The connector reuses the OIDC client from auth.oidc.providers and requests its own scopes. Tokens are stored per-user in logging.connector_db_path, encrypted with AES-256-GCM (key from auth.connector_encryption_key_env or auto-secret data/connector_secret).

connectors:
  gdrive:
    provider: google
    scopes: ["https://www.googleapis.com/auth/drive.file"]
  yandex_disk:
    provider: yandex
    scopes: ["cloud_api:disk.app_folder"]

Tools (kind: oauth_connect / kind: connector_request) are connected in agent.yaml via tool_files — see 05-tools-reference. Redirect URI for connectors: {base_url}/v1/connectors/callback.

widget — embeddable widget

widget:
  default_model: "drom-agent"   # агент по умолчанию

The only server-side setting is default_model: the agent the widget contacts when the model is not explicitly specified. Full breakdown of the widget (embedding, model selection order, demo, visual blocks) — in §19.

proxy — HTTP proxy restrictions

proxy:
  allow_hosts: []   # пусто = любой публичный хост

/proxy and /proxy/content always block private, loopback, link-local, and reserved ranges (including 169.254.169.254) on every redirect. allow_hosts additionally restricts by host (exact match or subdomain).

inbound — incoming webhook

inbound:
  token_env: ""   # env с bearer-токеном для POST /v1/inbound/:template (пусто = открыто, dev)

POST /v1/inbound/:template — external systems trigger a single agent turn synchronously. See 08-http-api.

approval — action approval (human-in-the-loop)

approval:
  required_tools: ["refund", "send_email", "delete_*"]  # glob-паттерны тулов, требующих одобрения
  operator_notify:              # доставка операторских запросов в мессенджер/webhook
    - type: telegram
      chat_id: -100123456789
      token_env: TG_OPERATOR_TOKEN
    - type: webhook
      url: https://ops.example.com/approvals
  context_template: |           # необязательный MiniJinja-шаблон контекста
    📝 {{ user_request }}
    {% for m in messages %}{{ m.role }}: {{ m.content }}{% endfor %}

modules — modules and native plugins

The platform is modular: features (workflow, cron, channels, docs, widget, approvals, …) and native extensions are connected as modules — linked crates or DLLs.

modules:
  enabled: [docs, widget, analytics]  # активировать на буте (+ requires транзитивно)
  auto_install: false                  # не ходить в сеть за артефактами на старте

See 20-native-plugins and 32-building-a-module.


3.3 config/budgets.yaml

budget_groups:
  - name: my-shared
    refill_per_sec: 50
    max_bucket: 50000
    max_total: 5000000        # null/отсутствует = без лимита
    warn_at: 0.8              # порог on_budget_threshold для группы
    per_user:                 # опционально
      refill_per_sec: 20
      max_bucket: 20000
      max_total: 500000
      warn_at: 0.8            # порог on_budget_threshold для per_user
    per_session:              # опционально
      refill_per_sec: 30
      max_bucket: 10000
      max_total: 200000

agents:
  - template: my-agent        # перекрывает budget_group / budget из agent.yaml
    group: my-shared
    budget:                   # опционально
      refill_per_sec: 10
      max_bucket: 200
      max_total: 1000

Budget fields

Field Description
refill_per_sec bucket refill, tokens/sec
max_bucket bucket capacity (burst)
max_total hard limit for the entire lifetime (absent = no limit)
warn_at fraction (0..1) of max_total at which the on_budget_threshold hook fires

warn_at can be set at each layer (группа / per_user / per_session), and the hook is called for the layer whose threshold was crossed (level = group / user / session).


3.4 Scheduled jobs (config/cron.yaml)

Cron runs an agent template on a schedule. They are defined in two places, both hot-reloaded:

jobs:
  - name: morning-digest
    template: rss
    schedule: "0 0 9 * * *"      # sec min hour dom month dow (UTC)
    prompt: "Резюмируй сегодняшние новости."
    notify: [{ type: log }]

Each job specifies exactly one of schedule / interval_secs / oneshot_at and delivers the result to a list of notify sinks (log, webhook, telegram, vk, yandex_messenger, messages_db, agent, email, workflow). History — in logs/cron.db, endpoint GET /v1/cron. Full reference — 11-cron-jobs.


3.5 Change monitors (config/monitors.yaml)

A monitor is a pull trigger: it watches an external source and starts an agent turn only when new items appear (unlike cron, which fires on a schedule regardless). Full description — Monitors.

monitors:
  - name: news-watch
    source:
      type: rss
      url: "https://lenta.ru/rss"
    template: rss-agent
    prompt: "Кратко резюмируй эти новые статьи:\n{{items}}"
    notify: [{ type: telegram, chat_id: 123456 }]
    poll_secs: 300

The monitor stores a cursor in logs/monitors.db: the first poll only "catches up" (saves the cursor, starts nothing), after that it starts only on items newer than the cursor.


3.6 Environment variables

Variable Purpose
DEEPSEEK_API_KEY / AGENT_OS_API_KEY LLM provider key
AGENT_OS_BIND bind address (default 127.0.0.1:3000)
AGENT_OS_URL public origin of the server behind a reverse proxy (e.g. https://platform.vectorres.ru). The host passes it to service agents as the base for server_url, so that links do not point to 127.0.0.1:3000. Takes priority over public_url: in config/server.yaml. If not set — derived from AGENT_OS_BIND
AGENT_OS_TLS_CERT / AGENT_OS_TLS_KEY paths to the PEM certificate and key for HTTPS
AGENT_OS_ADMIN_TOKEN admin tokens: comma-separated list, имя=токен. Read only if the variable name is specified in config/server.yaml → auth.admin_token_env: AGENT_OS_ADMIN_TOKEN (by default the field is empty, and the variable is ignored). Alternative — the auth.admin_tokens map
AGENT_OS_BENCH_MODE 1 = unlimited budgets (for local benchmark spawning)
AGENT_OS_CONFIG_REPO / _REF / _DIR / _POLL config source (see §3.1)
AGENT_OS_DISTRIB_DIR path to the distribution folder (default distrib)
(per agent) name from telegram.token_env Telegram bot token named in agent.yaml
(per agent) name from email.imap_password_env IMAP password for the email channel
(per agent) name from vk.token_env VK community group access token named in agent.yaml
(per agent) name from yandex_messenger.token_env Yandex Messenger bot OAuth token named in agent.yaml

.env is picked up automatically at startup (dotenv). It is in .gitignore, the template is .env.example.


3.7 Live reload

With kernel.live_reload: true (default), the server watches config/ and applies changes without a restart:

If the config comes from a git repository, with --config-poll / AGENT_OS_CONFIG_POLL the same path is applied after each new commit; an unchanged SHA is skipped.


3.8 HTTP request logging

Every HTTP request (method, path, status, duration, client IP, user-agent, X-User-Id, admin token name) is written to logs/access.jsonl and logs/access.db (both are disabled by an empty string in the corresponding logging field). The token value is not logged — only its name or the invalid marker.


3.9 ONNX service (security.onnx)

Local ONNX Runtime for NER / classification / embeddings. Requires building the server with the onnx feature. The ONNX Runtime core is not linked or shipped: it is loaded dynamically (load-dynamic) and resolved/downloaded on first use by the runtime block. Models are loaded lazily and downloaded on first use (pinned revision) unless download: never / offline: true.

security:
  onnx:
    models_dir: data/models
    download: auto            # auto | eager | never
    offline: false
    allow_hosts: []           # пусто = встроенные хосты HF (+ GitHub для runtime)
    runtime:                  # динамически загружаемое ядро ONNX Runtime
      # lib_path: /opt/onnxruntime/lib/libonnxruntime.so  # явный путь (без скачивания)
      version: "1.28.0"       # должно соответствовать ort (rc.13 → 1.28)
      variant: cpu            # cpu | gpu | cuda12 | cuda13 | directml
      # base_url: https://github.com/microsoft/onnxruntime/releases/download/v1.28.0
      # sha256: "..."
    models:
      - id: langid            # используется хуком language_guard (detector: onnx)
        task: text_classification
        # templates: [ngu-agent]        # per-agent селектор (необязательно)
        source:
          hf: { repo: onnx-community/language_detection-ONNX, revision: <commit>, path: onnx/model_quantized.onnx }
        tokenizer: tokenizer.json
        config: config.json
Field Description
models_dir artifact directory (cache; data/models/ in .gitignore); also ort/<version>/<variant>/
download auto (on first use), eager (at startup), never (local only)
offline disallow network (vendored models/ORT only)
allow_hosts allowed download hosts; empty = built-in HF hosts (for the default ORT source, GitHub hosts are added automatically)
runtime.lib_path explicit path to the ORT core (skips downloading)
runtime.version / variant ORT release and its variant (cpu/gpu/cuda12/cuda13/directml)
runtime.base_url / sha256 release mirror and archive checksum
models[].id id for OnnxService and hooks (onnx_model/ner_model)
models[].task ner | text_classification | embeddings
models[].source hf / http (+ sha256) / local
models[].tokenizer / config relative paths inside the source (tokenizer.json, vocab.txt, spm.model, …)
models[].templates restrict the model to agent templates (per-agent selector)
models[].labels / map label overrides / class mapping
models[].execution_provider / device_id EP (cpu/cuda/…) and GPU index; requires the agent_os_onnx feature

Admin endpoints: GET /v1/onnx/models, GET /v1/onnx/metrics, GET /v1/onnx/runtime.

The langid model (mini-BERT, ~25 MB int8, 200 languages) is registered by default and is used by the language_guard hook with detector: onnx (06 §6.18). If the service or model is unavailable, the hook is neutral.