yolop 0.12.1

Yolop — a terminal coding agent built on everruns-runtime
---
type: Product Specification
title: Configuration schema
description: Defines the configuration schema contract for Yolop.
---

# Configuration schema

Status: v1 implemented.

## Why

yolop's settings live in one TOML file (`settings.toml` in the platform config
dir). Loading is deliberately tolerant — unknown keys are ignored, never fatal
(see `Settings::from_table`) — so a user or another tool can add keys without
breaking yolop. The cost of that tolerance is that the file carries no
*semantics*: nothing tells the agent (or the user) what a key means, what type
it takes, what its default is, or how to set it safely.

The configuration schema fills that gap. It is an **informational** schema: it
never makes loading stricter, it adds meaning. That meaning is what lets the
agent edit yolop's own configuration the way a user describes it ("use anthropic
by default", "store my OpenAI key", "point at my local endpoint") instead of
forcing slash-command syntax.

## What

### Schema registry — single source of truth

`src/config/schema.rs` is a compile-time registry of `ConfigField`s. Each field
carries a canonical `key`, `aliases`, `title`, `description`, value `kind`
(`text` / `bool` / `secret`), effective `default`, `examples`, and whether it is
`provider_scoped` (addressed as `<key>.<provider>`). This one registry feeds
every configuration surface — the tools below and the `yolop-config` skill — so
there is no second place to keep in sync.

Keys are addressed the way a human would name them:

| Key                       | Type   | Meaning                                                        |
|---------------------------|--------|----------------------------------------------------------------|
| `default_provider`        | text   | Provider used when no `--provider` flag is given; takes precedence over env auto-detection. |
| `default_model`           | text   | Global fallback model spec for the active provider when no per-provider pick exists; only applied when the id is recognized for that provider. |
| `models.<provider>`       | text   | Per-provider model spec, survives provider switches.           |
| `tokens.<provider>`       | secret | Provider API token (owner-only on disk; env vars override).    |
| `base_urls.<provider>`    | text   | Endpoint base URL (used by the `custom` provider).             |
| `approval_mode`           | text   | Soft-approval paranoia level (`protective` / `normal` / `off`). |
| `approval_policy`         | text   | Hard shell approval policy (`untrusted` / `on-failure` / `on-request` / `never`). |
| `attribution`             | bool   | Commit/PR attribution on/off.                                  |
| `proactive_wake`          | bool   | Auto-start a turn when a background task finishes (TUI); on by default. |
| `worktrees`               | text   | Worktree isolation (`auto` / `always` / `off`).             |
| `sandbox_mode`            | text   | Shell containment (`read-only` / `workspace-write` / `danger-full-access`). |
| `capabilities`            | list   | Ordered `[[capabilities]]` harness overrides; `capabilities.<ref>` for schema metadata. |

`default_provider` is persisted under that name on disk; the legacy `provider`
key is still read (and accepted as an alias) so pre-rename settings files keep
working. `default_model` is applied as a cross-provider fallback in
`ProviderChoice::resolve_for_settings`, but only when the model id is
recognized for the active provider. At startup and on `/setup provider`
switches, yolop may also query the provider's models API (when credentials
exist) and fall back with a warning if the resolved model is no longer offered.
Per-provider `models.<provider>` picks are always trusted.

### Tools

The `config` capability (`src/capabilities/config.rs`) exposes two tools backed
by the schema:

- **`get_config`** — with no argument, returns every key with its semantics and
  current value; with a `key`, returns just that entry. Secrets are reported as
  `stored` / `unset`, never echoed. Use `key=capabilities` for the registered
  catalog plus stored overrides and effective harness, or `key=capabilities.<ref>`
  for one capability's schema metadata (`config_schema`, `config_ui_schema`).
- **`set_config`** — validates and persists scalar keys via `value` (`clear`
  unsets). Harness overrides: `key=capabilities` with a `json` object appends one
  `[[capabilities]]` entry; `value=clear` drops all stored overrides. Capability
  config is validated through each capability's `validate_config`.

Both honor aliases and validate provider segments against the supported-provider
list. Provider/model and capability edits take effect on the next run; `/setup`
remains the way to switch the *live* model mid-session.

### Configuration as a service

Configuration is exposed to the rest of the agent as a **service** so that
capabilities can read it without re-parsing the TOML or reaching into store
internals. `src/config/service.rs` defines the `ConfigService` trait:

- a generic `current(key)` that reads any value by its schema key (e.g.
  `models.openai`), with secrets reduced to `stored`/`unset`, and
- the two semantic getters that have dedicated consumers
  (`attribution_enabled`, `approval_mode`).

The surface is kept minimal: `current(key)` covers arbitrary reads, so a
capability adds a typed getter only when it grows a real need rather than
carrying speculative methods.

`SettingsStore` implements `ConfigService`, so the single shared handle that
backs writes also serves reads. Read-only capabilities take only an
`Arc<dyn ConfigService>`; write-coupled capabilities hold both — reads go
through the service handle, writes through the concrete `SettingsStore` — so the
read/write split is explicit at the type level. `AttributionCapability` reads
whether attribution is enabled through the service; `ApprovalCapability` reads
its soft-approval paranoia level through `ConfigService::approval_mode()` each
turn; the `config` capability's `get_config` reads single values through
`ConfigService::current`; and `SetupCapability` reads provider/token/model state
through its config handle while persisting `/setup` changes through the store.
`approval_mode` is also a first-class schema key, so `get_config`/`set_config`
manage it alongside everything else.

The per-target read helpers (`current_value`, `scoped_current`) live in the
service module so the `config` tools and any other consumer share one
implementation; secret redaction and unsupported-provider filtering therefore
apply uniformly wherever config is read.

### Context delivery

The schema reaches the agent two ways:

1. An always-on pointer: `ConfigCapability::system_prompt_contribution` adds a
   compact note (settings path + "use `get_config`/`set_config` or the
   `yolop-config` skill") to every turn.
2. The bundled `yolop-config` system skill is the
   detailed, on-demand reference. It instructs the agent to read the live schema
   via `get_config` rather than duplicating key lists, and points at the
   adjacent surfaces (`memory`, `yolop-hooks`, `/setup`).

## Boundaries

Configuration is distinct from the neighbouring personalization surfaces:
durable preferences are **memory**, behavioral rules are **hooks**, and
interactive live provider/model switching is **`/setup`**. All settings keys,
including harness capabilities, go through `get_config` / `set_config`.