rath.config#
Persistent local configuration for LLM providers, embedding/VLM provider selection, MCP servers, memory stores, and backend presets. v1.3.0 separates routing config from credentials and centralizes environment-variable declarations.
Source#
Module |
Source |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Public contract#
OpenRath resolves config in this order:
Config resolution starts with explicit Provider fields, then environment
variables, and finally the resolved .openrath/config.json store.#
Location |
When used |
|---|---|
|
Explicit override. |
|
Project-local marker directory exists. |
|
Default user config. |
The routing file is JSON. Unknown fields round-trip through the Pydantic models so newer OpenRath or third-party tools can add sections without losing data. API keys live in a sibling credentials.json after save.
config.json:
{
"version": 1,
"llm": {
"default_provider": "openai-main",
"providers": {
"openai-main": {
"provider_kind": "openai",
"model": "gpt-5.5",
"base_url": "https://api.openai.com/v1"
},
"claude": {
"provider_kind": "anthropic",
"model": "claude-sonnet-4-5"
},
"gemini": {
"provider_kind": "litellm",
"model": "gemini/gemini-2.0-flash"
}
}
},
"mcp": {
"default_enabled": ["filesystem"],
"servers": {
"filesystem": {
"command": ["python", "-m", "mcp_server_filesystem"],
"env": {}
}
}
},
"memory": {
"default_provider": "local-main",
"providers": {
"local-main": {
"backend_kind": "local",
"path": ".openrath/memory",
"embedding_provider": "openai-main",
"chat_provider": "openai-main"
}
}
},
"backend": {
"default_provider": "sandbox-main",
"providers": {
"sandbox-main": {
"backend_kind": "opensandbox",
"domain": "127.0.0.1:8080",
"options": {}
}
}
}
}
credentials.json (mode 0600 on POSIX):
{
"version": 1,
"llm": {
"providers": {
"openai-main": "sk-...",
"claude": "sk-ant-..."
}
},
"backend": {
"providers": {
"sandbox-main": "sandbox-key"
}
}
}
Legacy inline api_key values still load and take precedence over the sidecar.
The next save() migrates them. When another non-empty credential causes the
sidecar to be rewritten, keys set to None are omitted. If the resulting
credentials payload is empty, v1.3.0 does not remove or rewrite an existing
sidecar; remove that file or entry explicitly when revoking the last key.
OpenSandbox config boundary
In v1.3.0, backend domain participates in availability/config resolution but
is not injected into the SDK connection created by the backend, and backend
api_key has no runtime consumer. Configure actual connections with
OPEN_SANDBOX_DOMAIN and OPEN_SANDBOX_API_KEY (or the SDK’s
~/.sandbox.toml).
Store helpers#
API |
Behavior |
|---|---|
|
Loads the resolved default path or seeds an empty config. |
|
Splits secrets, atomically replaces each written file independently, sets user-only permissions on POSIX, writes |
|
Returns a named provider, or |
|
Finds the default matching provider, then the first matching provider. |
|
Returns a named local memory provider, or |
|
Returns a named backend provider, or |
|
Returns one MCP server entry. |
|
Resolves every name in |
Consumers#
Consumer |
Config behavior |
|---|---|
|
Builds a |
|
Uses |
|
Uses |
|
Builds local memory store options from |
|
Falls back to the first |
|
Falls back to the first |
|
Does not scan OpenRath config directly; use |
|
Builds MCP tool wrappers from one configured stdio server. |
Environment registry#
rath.config.env is the central registry for provider and OpenSandbox
environment variables. (OPENRATH_HOME remains path configuration.) It
preserves the existing precedence:
explicit field → environment → config.
API |
Behavior |
|---|---|
|
Returns one declared |
|
Returns a stripped value/default or |
|
Interprets |
|
Returns the first explicit non-empty value, then the environment tier. |
|
Returns sorted declarations. |
|
Produces stable documentation data without secret values. |
Autodoc#
- rath.config.resolve_config_dir() Path[source]#
Return the directory that holds
config.json.Raises
FileNotFoundErroronly whenOPENRATH_HOMEis set but points at a non-directory path that already exists (e.g. a regular file). A missing target is fine — the caller will create it on first save.
- rath.config.resolve_config_path() Path[source]#
Return the full path to
config.jsonunder the resolved config dir.
- rath.config.is_project_local(config_dir: Path) bool[source]#
Return whether
config_diris the project-local./.openrath/.Used by
rath.config.secretsto decide whether to also append.openrath/to the surrounding project’s.gitignoreon save.
- class rath.config.RathConfig(*, version: int = 1, llm: ~rath.config.schema.LLMConfig = <factory>, mcp: ~rath.config.schema.MCPConfig = <factory>, memory: ~rath.config.schema.MemoryConfig = <factory>, backend: ~rath.config.schema.BackendConfig = <factory>, **extra_data: ~typing.Any)[source]#
Top-level on-disk schema.
Sections currently in use:
llm,mcp,memory, andbackend. Unknown/future sections are preserved on round-trip becauseextra="allow".- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.LLMConfig(*, default_provider: str | None = None, embedding_provider: str | None = None, vlm_provider: str | None = None, providers: dict[str, ~rath.config.schema.LLMProviderConfig] = <factory>, **extra_data: ~typing.Any)[source]#
The
llmsection: named providers + which one is the default.default_provideris the chat fallback.embedding_providerandvlm_providerare independent overrides used byrath.llm.embedding.EmbeddingProviderandrath.llm.vlm.VLMProvider; when unset, those clients fall back todefault_provider’sapi_key/base_urlwith a sensible default model.- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.LLMProviderConfig(*, provider_kind: Literal['openai', 'anthropic', 'litellm'] = 'openai', model: str | None = None, api_key: str | None = None, base_url: str | None = None, temperature: float | None = None, max_tokens: int | None = None, **extra_data: Any)[source]#
One named entry under
llm.providers.Mirrors the most common
Providerfields. Less-common knobs (frequency_penalty,logit_bias, …) stay on explicitProvider(...)kwargs — adding fields here later is non-breaking thanks toextra="allow".- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.MCPConfig(*, default_enabled: list[str] = <factory>, servers: dict[str, ~rath.config.schema.MCPServerConfig] = <factory>, **extra_data: ~typing.Any)[source]#
The
mcpsection: named server defs + which are enabled by default.- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.MCPServerConfig(*, command: list[str], env: dict[str, str] = <factory>, **extra_data: ~typing.Any)[source]#
One named entry under
mcp.servers.commandis the full argv list passed to the stdio MCP server (the OpenRath adapter never shells out, so no string-form).envis merged into the subprocess environment by the adapter.- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.MemoryConfig(*, default_provider: str | None = None, providers: dict[str, ~rath.config.schema.MemoryProviderConfig] = <factory>, **extra_data: ~typing.Any)[source]#
The
memorysection: named local store presets.- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.MemoryProviderConfig(*, backend_kind: Literal['local'] = 'local', path: str | None = None, embedding_provider: str | None = None, chat_provider: str | None = None, **extra_data: Any)[source]#
One named entry under
memory.providers(local backend only).embedding_providerandchat_providername entries underllm.providersused byLocalMemoryBackendfor vector search and commit-time memo extraction respectively. OpenViking connection settings stay onMemoryStoreSpec.optionsor environment variables — they are not modeled here.- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.schema.BackendConfig(*, default_provider: str | None = None, providers: dict[str, ~rath.config.schema.BackendProviderConfig] = <factory>, **extra_data: ~typing.Any)[source]#
The
backendsection: named sandbox-backend presets + the default.- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.schema.BackendProviderConfig(*, backend_kind: ~typing.Literal['local', 'opensandbox'] = 'opensandbox', domain: str | None = None, api_key: str | None = None, options: dict[str, object] = <factory>, **extra_data: ~typing.Any)[source]#
One named entry under
backend.providers.Gives sandbox backends a config home parallel to
llm/memoryinstead of relying on environment variables and~/.sandbox.tomlalone.domainandapi_keyroute theopensandboxbackend;api_keyis a secret and is externalized tocredentials.jsonon save (seerath.config.credentials). Backend-specific knobs (image, timeout, …) stay onoptionsand round-trip viaextra="allow".- model_config: ClassVar[ConfigDict] = {'extra': 'allow'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class rath.config.env.EnvSpec(name: str, kind: ~rath.config.env.EnvKind, consumers: str, default: str | None = None, aliases: tuple[str, ...] = <factory>)[source]#
Declaration of a single environment variable.
- rath.config.env.env_value(name: str, *, environ: dict[str, str] | None = None) str | None[source]#
Return the stripped value of
name, or its default, orNone.Raises
KeyErrorifnameis not declared (typo guard). Whitespace-only values are treated as unset.
- rath.config.env.env_flag(name: str, *, environ: dict[str, str] | None = None) bool[source]#
Interpret
nameas a boolean flag (1/true/yes/on→ True).
- rath.config.env.resolve_env(name: str, *explicit: str | None) str[source]#
First non-empty among
explicitcandidates, then the env var.Mirrors
rath.llm.credentials.resolve_credential(), preserving the documentedexplicit > envprecedence. Returns""when nothing qualifies; callers decide whether that is an error. (Config-file fallback stays in the caller — the registry only owns the env tier.)
- rath.config.env.env_reference_markdown() str[source]#
Render the env reference as a stable markdown table (feeds the docs).
Secrets never print a default value (they have none), so the Default column stays blank for them — no secret material can leak into docs.
- class rath.config.ConfigStore(path: Path | None = None)[source]#
Round-trip the config file at
path.The constructor immediately reads the file (or seeds defaults when it does not exist), so callers do not need to guard against
FileNotFoundErrorseparately. Subsequent reads should mutateconfigdirectly; callsave()to persist.
- exception rath.config.ConfigError[source]#
Raised on schema-validation failure or corrupt JSON.
The string carries a human-readable summary; the original
json.JSONDecodeErrororpydantic.ValidationErroris available via__cause__.