> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Realm-scoped config, generation CAS, and surface-consistent runtime settings.

Configuration is realm-scoped. The active `realm_id` determines which raw
config document is read and written.

## Realm-scoped config

An ordinary non-global realm keeps these files in its resolved realm directory:

* `config.toml` (user settings)
* `config_state.json` (generation counter for CAS)
* `realm_manifest.json` (pinned backend + metadata)

The reserved `global` config document and its config-generation sidecar are the
exception: they are home-rooted at `~/.rkat/config.toml` and
`~/.rkat/config_state.json`. Global config can be inherited, but a global
realm's runtime state is never inherited.

For CLI commands, the normal non-global location is project-local:

```text theme={null}
<project-root>/.rkat/realms/<realm>/
```

Here `project-root` is the nearest ancestor containing `.rkat`. Outside an
existing project, it falls back to the exact invocation context. The invocation
context itself is never walked up for workspace realm identity or MCP trust
discovery, so identity and the local storage candidate are intentionally
separate facts. An explicit `--state-root` bypasses candidate probing.

For daemon/API surfaces that do not receive a `--state-root`, the default state
root is the platform data directory:

* macOS: `~/Library/Application Support/meerkat/realms/<realm>/`
* Linux: `~/.local/share/meerkat/realms/<realm>/`
* Windows: `%APPDATA%\meerkat\realms\<realm>\`

Use [realms](/concepts/realms) to choose whether state is shared or isolated.

## Realm config inheritance

A realm config doc may declare a `parent`, and resolution walks a single chain
`head -> parent -> ... -> global`. A terminal non-global realm appends
`global` when a global section is configured. The chain can also end before
`global` when no global config has been materialized.

```toml theme={null}
[realm.team]
parent = "org"
```

* **Config inherits** along the chain: models, MCP servers, hooks, skills,
  limits, and auth bindings are composed parent-first with child-wins.
* **State never inherits**: sessions, session stores, event stores, and the
  `.rkat/` projection stay realm-local to the consuming (head) realm.

The `global` realm's config document is **home-rooted** at `~/.rkat/config.toml`
(a single well-known location, not a per-workspace file), which is why a single
`rkat auth login` is inherited by every workspace realm via the chain tail. See
[Realm inheritance](/concepts/realm-inheritance) for the full model, including
credential read-inherit / write-strict-owner and the add/override-but-not-remove
rules for MCP servers and hooks.

## Effective precedence

For an agent build in a given realm, effective precedence is:

1. Built-in defaults
2. A configured `global` root and explicit ancestors, folded root-first
3. The head realm's own `config.toml`
4. Per-request runtime parameters (for example model, system prompt, or tool
   toggles on create/resume calls)

The agent build path on every surface (CLI, REST, RPC, MCP server) composes this
effective config over the head realm, so model/MCP/hook/skill/limit inheritance
applies on every build, not just the auth path. `config get` / `config set`
operate on the **raw head doc** (a read/write split), so an inherited entry is
read through composition but never flattened into the child's own doc on write.

Environment variables still matter, but they are primarily part of **runtime credential resolution**, not a general config layer that mutates the loaded realm config. In practice:

* realm config remains the canonical persisted settings surface
* provider credentials can still come from `RKAT_*` or provider-native env vars at resolve time
* bindings and auth profiles decide how those credentials are used at runtime

## Config APIs and CAS

RPC, REST, and MCP all expose the same config envelope:

* `config`
* `generation`
* `realm_id`
* `instance_id`
* `backend`

Some surfaces also expose `resolved_paths` in diagnostic/admin contexts. Treat that field as optional rather than universal across all public config envelopes.

Writes support optimistic concurrency:

* `config/set` and `config/patch` accept optional `expected_generation`
* stale writes return generation conflict

This gives deterministic behavior when multiple clients or processes update config concurrently.

## Merge semantics

Meerkat uses three update models:

1. Layered config loading (`defaults -> file -> runtime`) uses **field-wise merge**:

* scalar/option values: last non-default wins
* section values (for example `store`, `comms`, `compaction`): replace whole section
* hook entries: append/extend

2. Realm-chain composition folds each realm doc parent-first with **child-wins**:

* per-provider model defaults: union, child-wins per provider (a child overrides its anthropic default while inheriting the parent's openai default)
* MCP servers: map-key union, child-wins per name (add or override, never remove)
* hooks and skill sources: append parent-first (child cannot remove inherited entries)
* limits and other config sections: per-field child-wins (a child can tighten `max_sessions` while inheriting other caps)
* the binding/backend/auth maps are **not** merged across realms: each realm's binding resolves its `backend_profile` / `auth_profile` only within its own section (the owning realm)

3. Runtime patch APIs (`config/patch`, `PATCH /config`) use **RFC 7396 JSON merge-patch** semantics.

Use `config/set` when replacing the whole config intentionally, and `config/patch` for surgical updates.

### Presence-based override (Option fields)

`Config.max_tokens` and `agent.max_tokens_per_turn` are optional. `None` means
"inherit / use the template default" and is resolved at point-of-use
(`resolved_max_tokens` / `resolved_max_tokens_per_turn`). Because merge is
**presence-based** (an explicit value wins, regardless of whether it equals the
default), a child realm can override a non-default parent value back **down to
the default** - something a `!= default` heuristic could not express.

## CLI behavior

CLI config commands operate on the selected realm:

```bash theme={null}
rkat --realm team-alpha config get --format json
rkat --realm team-alpha config get --format json --with-generation
rkat --realm team-alpha config patch --json '{"agent":{"model":"gpt-5.5"}}' --expected-generation 4
```

Without `--realm`, CLI derives a stable workspace realm (`ws-...`) from the
current directory, or from `--context-root` when supplied.

## What About `~/.rkat/config.toml` And `.rkat/config.toml`?

`~/.rkat/config.toml` is the home-rooted config document for the reserved
`global` realm. `rkat auth login` writes the `[realm.global]` binding
section here; once configured, it is the implicit chain tail for workspace
realms.

The project-root `.rkat/config.toml` still exists for `rkat init`, legacy
single-file loading, and explicitly layered hook discovery. Do not confuse it
with the active non-global realm document at
`<state-root>/<realm>/config.toml`, which is the canonical raw head store used
by CLI/RPC/REST/MCP runtime composition.

## Backend and storage settings

Session backend is pinned per realm in `realm_manifest.json` (`sqlite`, `jsonl`, or `memory`).
`--realm-backend` only matters on first creation of a realm.

## Compaction settings

Compaction runtime policy is configurable in realm config:

```toml theme={null}
[compaction]
auto_compact_threshold = 100000
# max_request_bytes = 9000000 # optional operator override
recent_turn_budget = 4
max_summary_tokens = 4096
min_turns_between_compactions = 3
```

When `session-compaction` is enabled, `AgentFactory` maps these values into
the runtime `CompactionConfig`. If `auto_compact_threshold` is omitted,
cataloged models scale the token trigger to 80 percent of their context window.
Built-in provider clients also attach the current provider's approximate
request-byte cap to each lowered request; the byte trigger fires at 80 percent
of that cap. `max_request_bytes` is an optional operator override, especially
useful for a self-hosted gateway whose limit is not in the catalog.

Compaction is checked against both token and serialized-request pressure.
Inline media can exhaust a provider byte limit long before the token estimate
reaches its threshold.

## Retry and stream watchdog settings

LLM retry policy (`RetryPolicy`) is configurable in realm config:

```toml theme={null}
[retry]
max_retries = 3
initial_delay = "500ms"
max_delay = "30s"
multiplier = 2.0
call_timeout = "45s"                 # omit to inherit the model-profile default
stream_inactivity_timeout = "300s"   # "disabled" turns the watchdog off
```

`call_timeout` is a tri-state override for the hard per-LLM-call deadline:
omitted inherits the profile-derived default, `"disabled"` turns it off, and
a duration sets it explicitly.

`stream_inactivity_timeout` is the per-provider-stream inactivity
watchdog. It is **on by default** with a 300-second window: an LLM call
whose stream reports no events for the window is aborted with the retryable
`StreamStalled` failure and flows through normal retry handling. Each received stream
event re-arms the window, so long-but-alive responses are unaffected. Set
`stream_inactivity_timeout = "disabled"` to opt out, or a duration
(`"120s"`) for an explicit window. Unlike `call_timeout` it never inherits
from the model profile; omitted means the built-in 300s default. The
watchdog covers all built-in provider clients; a custom `AgentLlmClient`
that does not report stream liveness fails open (no watchdog; only the
hard call timeout applies).

Unknown provider failures also enter the machine-authorized bounded retry path.
Explicit terminal classes such as invalid requests, authentication failures,
missing models, content filtering, context overflow, and oversized requests
remain non-retryable. With the default `max_retries = 3`, a persistently
unknown failure can make four provider attempts before returning
`retry_exhausted`.

## Turn and agent-lifetime bounds

The timeouts above bound one *segment* of a turn: a single LLM call, a single
provider stream, a single tool call. Their **sum** is a separate fact with a
separate owner:

```toml theme={null}
[limits]
budget = 200000              # max tokens, agent lifetime
max_duration = "4h"          # wall-clock ceiling for the AGENT'S LIFETIME
max_turn_duration = "30m"    # aggregate wall-clock ceiling for ONE TURN
```

`max_turn_duration` is the aggregate bound. Its clock is re-armed at every run
entry, so it measures one turn end-to-end no matter how many LLM calls,
retries, and tool batches that turn contains. Exceeding it takes the existing
time terminal, `TurnTerminalOutcome::TimeBudgetExceeded`, which surfaces as
`AgentError::TerminalFailure { outcome: TimeBudgetExceeded, .. }`. Note the
asymmetry with the other budget dimensions, which is deliberate: an exhausted
token or tool-call budget is an orderly stop that still answers the caller,
while an exhausted deadline is a hard failure. A turn past its deadline can no
longer promise what it did or when it will answer, so it fails closed rather
than reporting a warning and continuing.

`max_duration` is **not** a per-turn deadline. Its clock starts when the
session's agent is built and is never re-armed, so it measures the agent's
whole lifetime including idle time between turns. Setting it low to bound a
turn will terminalize a later turn that did no work.

Both are unset by default: turns are unbounded in aggregate unless a
deployment declares a ceiling. The bound is enforced at segment boundaries, so
it never tears down a tool call that is already running; the effective ceiling
is `max_turn_duration` plus the longest segment already in flight (at most one
per-tool-call timeout, since calls in a batch run their clocks together).

`limits.max_sessions` controls active-session admission; persisted history
does not consume the limit. RPC, REST, and MCP snapshot this value when their
session service builds its admission semaphore. Changing config does not resize
an already-running service; rebuild or restart it to apply the new limit.

## HTML Presentation Defaults

HTML rendering is surface-requested; config chooses the default template and
optional custom templates:

```toml theme={null}
[presentation.html]
default_template = "polished"

[presentation.html.templates.incident]
path = "./templates/incident.html"
```

This does not turn ordinary runs into HTML. The CLI must still request
`--browser` or the corresponding presentation mode.

## Model fallback settings

The model fallback chain is realm-scoped configuration. Empty `chain` means
"use the catalog default chain"; a non-empty chain is an explicit ordered
operator policy:

```toml theme={null}
[model_fallback]
enabled = true

[[model_fallback.chain]]
model = "claude-opus-4-8"
provider = "anthropic"

[[model_fallback.chain]]
model = "gpt-5.5"
provider = "openai"
auth_binding = { realm = "global", binding = "openai_oauth" }
```

A fallback target is identified by model, provider, self-hosted server, and
`auth_binding`. This means an explicit custom chain can retry the same
model/provider through a different credential binding. Catalog-default chains
stay inside the selected non-env auth realm and skip providers that are not
registered there.

Use `enabled = false` to disable runtime model failover. If a lower-precedence
layer disabled fallback or supplied a custom chain, set
`use_catalog_default_chain = true` in a higher-precedence layer to restore the
built-in catalog chain.

Fallback activation is still runtime-governed: the generated recovery
authority must classify the LLM failure as recoverable, and the core run loop
must decide the retry is pre-stream safe. Network/call timeouts never trigger
model fallback, and cross-model fallback is suppressed after user-visible text
or reasoning stream output has been emitted. On activation, Meerkat recomputes
the active model's tool capability filter and token limits before retrying.

## See also

* [Realms](/concepts/realms)
* [Realm inheritance](/concepts/realm-inheritance)
* [Sessions](/concepts/sessions)
* [CLI configuration](/cli/configuration)
