Profiles¶
Profiles are saved configurations for AGRO’s environment and repositories.
They’re just JSON files on disk, but they sit on top of the configuration
registry so they don’t have to know about .env vs agro_config.json
vs Pydantic defaults.
This page explains how profiles interact with the new config registry and the
service layer (config_store, editor, indexing, keywords, rag,
traces).
TL;DR
- Profiles are still file-based JSON under
GUI_DIR/profiles. - The config registry is now the single source of truth at runtime.
- Service code never reads JSON directly – it always goes through the registry.
- Profiles are a way to write config, not a second config system.
Where profiles live¶
Profiles are stored under the GUI directory:
The path is derived from gui_dir in common/paths.py, so if you move the web
assets, the profile location follows.
Each profile is a JSON object whose keys are a subset of AGRO_CONFIG_KEYS
(from server/models/agro_config_model.py).
How profiles relate to the config registry¶
The config registry (server/services/config_registry.py) is the thing the
backend actually uses. It merges three sources with a fixed precedence:
.envfile (loaded viapython-dotenv)agro_config.json(validated by Pydantic)- Pydantic defaults (hard-coded fallbacks)
Profiles sit outside that stack. They’re a way to produce or update
agro_config.json (and sometimes .env), but once the server is running,
all reads go through the registry.
flowchart LR
subgraph Disk
A[.env]
B[agro_config.json]
C[profiles/*.json]
end
C -->|write / update| B
A --> D@{ shape: cyl, label: "Config Registry" }
B --> D
D --> S1[server/services/editor.py]
D --> S2[server/services/indexing.py]
D --> S3[server/services/keywords.py]
D --> S4[server/services/rag.py]
D --> S5[server/services/traces.py]
The important bit: services never parse profile JSON. They only call
get_config_registry() and use typed accessors like get_int, get_bool,
get_str.
Service layer: how config is actually used¶
This section is here so you can see what a profile needs to set to affect real behavior.
Configuration registry¶
server/services/config_registry.py is the central piece:
- Loads
.envfirst (viaload_dotenv(override=True)). - Loads and validates
agro_config.jsonintoAgroConfigRoot. - Exposes a thread-safe registry with helpers:
get_int(key, default)get_float(key, default)get_bool(key, default)get_str(key, default)- Tracks where each value came from (env vs config vs default).
- Supports legacy aliases like
MQ_REWRITES → MAX_QUERY_REWRITES.
Profiles don’t talk to this directly; the API / UI does. But everything else in the server only talks to the registry.
Config store & secrets¶
server/services/config_store.py is the thin layer that reads/writes
agro_config.json and exposes it to the API / UI.
Key points:
- Uses
_load_repos_rawandrepo_root()to find the right config file. - Validates writes against the same Pydantic model as the registry.
- Writes are done via
_atomic_write_textto avoid partial files, with a Docker‑friendly fallback whenos.replace()fails on watched volumes. - Sensitive keys are masked using
SECRET_FIELDS:
When you save a profile from the UI, it will:
- Update
agro_config.json(non-secret knobs). - Optionally update
.envfor infrastructure / secret values. - Use the config registry on the next request – no direct profile reads.
Editor service¶
server/services/editor.py exposes a small settings surface for the built-in
code editor. It shows how the registry is supposed to be used from services:
If you want a profile to change the editor port or binding, you set those keys
in the profile → they end up in agro_config.json → the registry sees them.
Indexing service¶
server/services/indexing.py is the entry point for (re)building the index.
It uses the registry once at module import time:
What this means for profiles:
REPOis the main knob that determines which codebase is indexed.- Flags like
ENRICH_CODE_CHUNKSare passed via environment variables.
If you want a profile per repo, you typically:
- Set
REPOin the profile. - Optionally set enrichment / chunking knobs that the indexer reads from
agro_config.json.
Keyword extraction service¶
server/services/keywords.py caches a handful of tuning parameters at module
load time:
Profiles that care about discriminative keywords can set these keys. If you
change them at runtime, call reload_config() (the UI already does this when
applying config changes).
RAG service¶
server/services/rag.py is the HTTP entry point for retrieval + generation.
It uses the registry for things like FINAL_K (how many chunks to return):
If you want a “deep” profile vs a “fast” profile, this is where you tune:
FINAL_K/LANGGRAPH_FINAL_K- Any other retrieval / reranking knobs defined in
AgroConfigRoot.
Traces service¶
server/services/traces.py is intentionally simple – it just lists and reads
trace JSON files from out/<repo>/traces:
This is mostly driven by REPO again. Profiles that switch repos will see a
different trace directory.
How the web UI uses profiles¶
The React components under web/src/components are wired to the same HTTP
endpoints that talk to config_store and the registry. A few relevant ones:
Admin/GeneralSubtab.tsx– global settings, repo selection, etc.Dashboard/EmbeddingConfigPanel.tsx– embedding / retrieval knobs.DevTools/Editor.tsx– editor settings (port, bind, enable/disable).Infrastructure/MCPSubtab.tsx– MCP / tool integration config.
When you:
- Select a profile in the UI
- Edit settings
- Click “Save”
…the UI will:
- POST the merged config to the backend.
- Backend validates and writes
agro_config.json(and.envif needed). - Config registry is reloaded.
- Services that cache values (like
keywords) are told toreload_config().
You don’t have to think about .env vs JSON vs defaults – the UI and
config_store handle that.
Designing profile JSON¶
Profiles are not required to contain every key. Anything missing will fall back to:
.env(for infra / secrets)agro_config.json(previous values)- Pydantic defaults
A minimal profile that just switches repos might look like:
| web/public/profiles/my-repo.json | |
|---|---|
A more “infra-heavy” profile (for a cloud-backed setup) might also be
accompanied by a .env file that sets:
The profile doesn’t need to know about those – it just sets the RAG knobs.
When to use profiles vs .env¶
Use a profile when you want to:
- Switch between repos (
REPO). - Change retrieval / reranking / keyword tuning.
- Toggle optional features (editor, MCP, tracing, evaluation settings).
- Save a “baseline” configuration for evaluation runs.
Use .env when you want to:
- Change infrastructure: ports, hostnames, volumes.
- Set secrets: API keys, OAuth tokens.
- Override low-level behavior that must be available before Pydantic loads
agro_config.json.
The config registry is designed so you can mix both without surprises.
Extending profiles for new features¶
If you add a new feature and want it to be profile‑aware, the pattern is:
- Add a field to
AgroConfigRoot/AGRO_CONFIG_KEYS. - Use the registry in your service:
- Expose it in the UI under an appropriate tab.
- Profiles can now set
"MY_FEATURE_ENABLED": trueand everything works.
You don’t need a separate “profile model” – the Pydantic config model is the schema.
Summary¶
- Profiles are still simple JSON files, but they now sit on top of a centralized, Pydantic‑validated config registry.
- All backend services (
editor,indexing,keywords,rag,traces, etc.) read from the registry, not from profile files. - The web UI and
config_storeare responsible for mapping profile edits intoagro_config.jsonand.env. - If you stick to
get_config_registry()in new code, profiles “just work” without extra plumbing.