13#include <nlohmann/json.hpp>
23namespace entropic::config {
35 ryml::ConstNodeRef node,
40 if (
extract(node,
"path", path_str)) {
61 std::string mmproj_str;
62 if (
extract(node,
"mmproj", mmproj_str)) {
79 ryml::ConstNodeRef node,
91 std::string grammar_str;
92 if (
extract(node,
"grammar", grammar_str)) {
96 std::string auto_chain_str;
97 if (
extract(node,
"auto_chain", auto_chain_str)) {
101 bool routable_val =
false;
102 if (
extract(node,
"routable", routable_val)) {
127 ryml::ConstNodeRef node,
133 if (node.has_child(
"router")) {
138 return "models.router: " + err;
142 for (
auto child : node) {
143 std::string key =
to_string(child.key());
144 if (key ==
"default" || key ==
"router") {
147 if (!child.is_map()) {
152 if (config.
tiers.count(key) > 0) {
153 tier = config.
tiers[key];
157 return "models." + key +
": " + err;
159 config.
tiers[key] = std::move(tier);
174 ryml::ConstNodeRef node,
180 std::string class_prompt;
181 if (
extract(node,
"classification_prompt", class_prompt)) {
200 ryml::ConstNodeRef node,
210 extract(node,
"warning_threshold_percent",
224 ryml::ConstNodeRef node,
242 ryml::ConstNodeRef node,
252 if (
extract(node,
"max_read_bytes", max_read)) {
268 ryml::ConstNodeRef node,
274 if (node.is_map() && node.has_child(
"socket_path")
275 && !node[
"socket_path"].val_is_null()) {
276 std::filesystem::path tmp;
293 ryml::ConstNodeRef node,
305 if (node.has_child(
"filesystem")) {
308 if (node.has_child(
"external")) {
324 ryml::ConstNodeRef node,
342 ryml::ConstNodeRef node,
360 ryml::ConstNodeRef node,
366 int max_bytes_int = 0;
367 if (
extract(node,
"max_bytes", max_bytes_int)) {
368 config.
max_bytes =
static_cast<size_t>(max_bytes_int);
382 ryml::ConstNodeRef node,
413 ryml::ConstNodeRef node,
423 if (node.has_child(
"draft")) {
425 node[
"draft"], registry, config.
draft);
427 s_log->warn(
"inference.speculative.draft parse: {}", err);
449 ryml::ConstNodeRef root,
453 if (!root.has_child(
"inference")) {
return; }
454 auto inf = root[
"inference"];
455 if (inf.has_child(
"prompt_cache"))
457 if (inf.has_child(
"speculative"))
471 ryml::ConstNodeRef root,
475 if (root.has_child(
"generation"))
477 if (root.has_child(
"permissions"))
479 if (root.has_child(
"mcp"))
481 if (root.has_child(
"compaction"))
483 if (root.has_child(
"lsp"))
486 if (root.has_child(
"constitutional_validation"))
488 root[
"constitutional_validation"],
524 ryml::ConstNodeRef root,
542 ryml::ConstNodeRef root,
547 if (root.has_child(
"models")) {
550 if (err.empty() && root.has_child(
"routing")) {
574 const std::filesystem::path& path,
579 if (content.empty()) {
580 return "cannot read config file: " + path.string();
583 ryml::Tree tree = ryml::parse_in_arena(
584 ryml::to_csubstr(path.string()),
585 ryml::to_csubstr(content));
586 ryml::ConstNodeRef root = tree.rootref();
587 if (!root.is_map()) {
588 return "config file root is not a YAML mapping: " + path.string();
604 const std::filesystem::path& global_path,
609 auto bundled = data_dir /
"default_config.yaml";
610 if (!std::filesystem::exists(bundled)) {
611 s_log->warn(
"No config found (checked global, project, bundled)");
614 s_log->info(
"No user config — loading bundled default: {}",
617 if (!err.empty()) {
return "bundled default: " + err; }
620 if (!std::filesystem::exists(global_path)) {
621 auto parent = global_path.parent_path();
622 std::filesystem::create_directories(parent);
623 std::filesystem::copy_file(bundled, global_path);
624 s_log->info(
"Created {}", global_path.string());
640 const std::filesystem::path& global_path,
641 const std::filesystem::path& project_path,
645 bool have_config =
false;
648 if (std::filesystem::exists(global_path)) {
649 s_log->info(
"Loading global config: {}", global_path.string());
651 if (!err.empty()) {
return "global config: " + err; }
655 if (err.empty() && std::filesystem::exists(project_path)) {
656 s_log->info(
"Loading project config: {}", project_path.string());
658 if (!err.empty()) {
return "project config: " + err; }
662 return have_config ?
""
677 const std::filesystem::path& global_path,
678 const std::filesystem::path& project_path,
683 if (!err.empty()) {
return err; }
687 std::vector<std::string> warnings;
689 for (
const auto& w : warnings) { s_log->warn(
"{}", w); }
703 const std::filesystem::path& path,
714 std::vector<std::string> warnings;
716 for (
const auto& w : warnings) {
717 s_log->warn(
"{}", w);
731 const std::string& name,
732 const nlohmann::json& entry) {
734 std::string type = entry.value(
"type", std::string(
"stdio"));
736 result.
url = entry.value(
"url", std::string{});
738 result.
command = entry.value(
"command", std::string{});
739 if (entry.contains(
"args") && entry[
"args"].is_array()) {
740 for (
const auto& a : entry[
"args"]) {
741 result.
args.push_back(a.get<std::string>());
744 if (entry.contains(
"env") && entry[
"env"].is_object()) {
745 for (
auto it = entry[
"env"].begin();
746 it != entry[
"env"].end(); ++it) {
747 result.
env[it.key()] = it.value().get<std::string>();
751 s_log->info(
"Discovered external MCP server: {} (type={})",
779 const std::filesystem::path& path) {
780 std::ifstream f(path);
783 std::stringstream ss;
785 j = nlohmann::json::parse(ss.str(),
nullptr,
false);
787 bool valid = f.is_open() && !j.is_discarded() && j.is_object()
788 && j.contains(
"mcpServers")
789 && j[
"mcpServers"].is_object();
791 s_log->warn(
".mcp.json missing/malformed: {}", path.string());
794 return j[
"mcpServers"];
803 const std::filesystem::path& project_dir,
805 if (project_dir.empty()) {
return; }
806 auto path = project_dir /
".mcp.json";
807 if (!std::filesystem::exists(path)) {
return; }
809 if (!servers) {
return; }
812 for (
auto it = servers->begin(); it != servers->end(); ++it) {
813 const std::string& name = it.key();
815 s_log->info(
".mcp.json: {} already in config, skipping", name);
822 s_log->info(
"Loaded {} external MCP server(s) from {}",
823 added, path.string());
835 const char* home = getenv(
"HOME");
838 auto path = std::filesystem::path(home) /
".entropic" /
"config.yaml";
839 if (std::filesystem::exists(path)) {
840 s_log->info(
"Loading global config: {}", path.string());
843 err =
"global config: " + err;
869 const std::filesystem::path& consumer_defaults)
872 if (consumer_defaults.empty()
873 || consumer_defaults.is_absolute()
874 || std::filesystem::exists(consumer_defaults)) {
875 return consumer_defaults;
880 std::filesystem::path result = consumer_defaults;
883 && info.dli_fname !=
nullptr) {
885 auto lib_path = std::filesystem::absolute(info.dli_fname, ec);
887 auto candidate = lib_path.parent_path().parent_path()
888 /
"share" /
"entropic" / consumer_defaults.filename();
889 if (std::filesystem::exists(candidate)) {
913 if (content.empty()) {
return false; }
914 auto tree = ryml::parse_in_arena(
915 ryml::to_csubstr(path.string()),
916 ryml::to_csubstr(content));
917 auto root = tree.rootref();
918 return root.is_map() && root.has_child(ryml::to_csubstr(key));
936 const std::filesystem::path& consumer_defaults_in,
940 if (consumer_defaults.empty() || !std::filesystem::exists(consumer_defaults)) {
943 s_log->info(
"Loading consumer defaults: {}", consumer_defaults.string());
948 s_log->info(
"Consumer defines models: — replacing global tiers");
959 s_log->warn(
"Consumer defaults failed: {}", err);
981 const std::filesystem::path& project_dir,
985 if (!project_dir.empty()) {
986 auto path = project_dir /
"config.local.yaml";
987 if (std::filesystem::exists(path)) {
988 s_log->info(
"Loading project config: {}", path.string());
990 s_log->info(
"Project defines models: — replacing prior tiers");
1000 err =
"project config: " + err;
1034 const std::filesystem::path& project_dir,
1035 const std::filesystem::path& consumer_defaults,
1042 if (!project_dir.empty() && config.
log_dir.empty()) {
1071 const std::string& content,
1075 if (content.empty()) {
1076 return "config string is empty";
1079 ryml::Tree tree = ryml::parse_in_arena(
1080 ryml::to_csubstr(
"<json>"),
1081 ryml::to_csubstr(content));
1082 auto root = tree.rootref();
1083 if (!root.is_map()) {
1084 return "config string root is not a mapping";
1088 if (root.has_child(
"models")) {
1091 if (err.empty() && root.has_child(
"routing")) {
1110 const std::string& content,
1121 std::vector<std::string> warnings;
1123 for (
const auto& w : warnings) {
1124 s_log->warn(
"{}", w);
Bundled model registry loaded from bundled_models.yaml.
std::filesystem::path resolve(const std::string &value) const
Resolve a model reference to a filesystem path.
static void extract_scalar_fields(ryml::ConstNodeRef root, ParsedConfig &config)
Extract the top-level scalar/path config fields.
static std::string parse_config_string(const std::string &content, const BundledModels ®istry, ParsedConfig &config)
Parse a config string (YAML or JSON) and overlay onto config.
static std::string load_project_layer(const std::filesystem::path &project_dir, const BundledModels ®istry, ParsedConfig &config)
Load the project-local layer if present.
static std::string parse_external_mcp_config(ryml::ConstNodeRef node, ExternalMCPConfig &config)
Parse the external MCP section from a YAML node.
static bool yaml_has_key(const std::filesystem::path &path, const char *key)
Load the consumer/app defaults layer if present (non-fatal).
static std::string load_global_layer(const BundledModels ®istry, ParsedConfig &config)
Load the global user config layer if present.
static std::string parse_generation_config(ryml::ConstNodeRef node, GenerationConfig &config)
Parse the generation section from a YAML node.
static std::string parse_permissions_config(ryml::ConstNodeRef node, PermissionsConfig &config)
Parse the permissions section from a YAML node.
static void parse_inference_subsections(ryml::ConstNodeRef root, const BundledModels ®istry, ParsedConfig &config)
Parse the nested optional config sub-sections.
static void load_consumer_layer(const std::filesystem::path &consumer_defaults_in, const BundledModels ®istry, ParsedConfig &config)
Load the consumer-defaults layer.
static std::filesystem::path resolve_consumer_defaults(const std::filesystem::path &consumer_defaults)
Resolve a relative consumer_defaults path against install prefix.
static std::optional< nlohmann::json > read_mcp_servers(const std::filesystem::path &path)
Discover and parse .mcp.json from the project directory.
static void parse_optional_sections(ryml::ConstNodeRef root, const BundledModels ®istry, ParsedConfig &config)
Parse optional config sections that don't return errors.
static void parse_speculative_config(ryml::ConstNodeRef node, const BundledModels ®istry, SpeculativeConfig &config)
Parse inference.speculative YAML node into SpeculativeConfig.
static void parse_constitutional_validation_config(ryml::ConstNodeRef node, ConstitutionalValidationConfig &config)
Parse constitutional_validation section.
static void parse_optional_subsections(ryml::ConstNodeRef root, const BundledModels ®istry, ParsedConfig &config)
Parse the nested optional config sub-sections.
static std::string parse_mcp_config(ryml::ConstNodeRef node, MCPConfig &config)
Parse the MCP section from a YAML node.
static std::string load_bundled_default(const std::filesystem::path &global_path, const BundledModels ®istry, ParsedConfig &config)
Load bundled default config when no user config exists.
static std::string parse_filesystem_config(ryml::ConstNodeRef node, FilesystemConfig &config)
Parse the filesystem section from a YAML node.
static std::string parse_compaction_config(ryml::ConstNodeRef node, CompactionConfig &config)
Parse the compaction section from a YAML node.
static void discover_mcp_json(const std::filesystem::path &project_dir, ParsedConfig &config)
Discover + merge external MCP servers from <dir>/.mcp.json.
static std::string parse_top_sections(ryml::ConstNodeRef root, const BundledModels ®istry, ParsedConfig &config)
Parse the top-level models/routing/optional sections.
static std::string parse_routing_config(ryml::ConstNodeRef node, RoutingConfig &config)
Parse the routing section from a YAML node.
static std::string parse_prompt_cache_config(ryml::ConstNodeRef node, PromptCacheConfig &config)
Parse prompt_cache config from inference YAML section.
static std::string parse_model_config(ryml::ConstNodeRef node, const BundledModels ®istry, ModelConfig &config)
Parse a ModelConfig from a YAML node.
static ExternalServerEntry parse_mcp_json_entry(const std::string &name, const nlohmann::json &entry)
Parse a single mcpServers entry from .mcp.json.
static std::string load_config_layers(const std::filesystem::path &global_path, const std::filesystem::path &project_path, const BundledModels ®istry, ParsedConfig &config)
Load global, project, and bundled config layers in order.
static std::string parse_tier_config(ryml::ConstNodeRef node, const BundledModels ®istry, TierConfig &config)
Parse a TierConfig from a YAML node.
static std::string parse_models_config(ryml::ConstNodeRef node, const BundledModels ®istry, ModelsConfig &config)
Parse the models section from a YAML node.
static std::string parse_lsp_config(ryml::ConstNodeRef node, LSPConfig &config)
Parse the LSP section from a YAML node.
Config loader — YAML to C++ structs with validation.
ENTROPIC_EXPORT std::string load_config(const std::filesystem::path &global_path, const std::filesystem::path &project_path, const BundledModels ®istry, ParsedConfig &config)
Load config using layered resolution.
ENTROPIC_EXPORT std::string load_config_from_string(const std::string &content, const BundledModels ®istry, ParsedConfig &config)
Load config from a YAML/JSON string (no layering).
ENTROPIC_EXPORT std::filesystem::path resolve_data_dir(const ParsedConfig &config)
Resolve the bundled data directory.
ENTROPIC_EXPORT std::string parse_config_file(const std::filesystem::path &path, const BundledModels ®istry, ParsedConfig &config)
Parse a config YAML file and overlay onto existing config.
ENTROPIC_EXPORT void apply_env_overrides(ParsedConfig &config)
Apply ENTROPIC_* environment variable overrides.
ENTROPIC_EXPORT std::string load_config_from_file(const std::filesystem::path &path, const BundledModels ®istry, ParsedConfig &config)
Load config from a single YAML file (no layering).
ENTROPIC_EXPORT std::string load_layered(const std::filesystem::path &project_dir, const std::filesystem::path &consumer_defaults, const BundledModels ®istry, ParsedConfig &config)
Load config with consumer defaults + global + project layers.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Auto-compaction configuration.
bool save_full_history
Save full history before compaction.
bool notify_user
Notify user on compaction.
float warning_threshold_percent
Warning trigger (0.3–0.9)
int preserve_recent_turns
Turns to preserve (1–10)
int summary_max_tokens
Summary max tokens (500–4000)
int tool_result_ttl
Tool result TTL in turns (>= 1; v2.1.3 #6: gated on fill, no upper bound)
float threshold_percent
Compaction trigger (0.5–0.99)
bool enabled
Enable auto-compaction.
Constitutional validation pipeline configuration.
int max_revisions
Max re-generation attempts (0 = critique only)
int priority
Hook priority (higher = later)
bool enable_thinking
Enable think-blocks for critique (default OFF)
float temperature
Critique generation temperature.
bool enabled
Global enable/disable (default OFF)
int max_critique_tokens
Token budget for critique generation.
std::string grammar_key
Grammar registry key.
std::vector< std::string > skip_tiers
Tiers exempt from validation (default: lead — streams before hook fires)
External MCP server configuration (Entropic-as-server).
std::optional< std::filesystem::path > socket_path
Socket path (nullopt = derived)
int rate_limit
Requests per minute (1–100)
bool enabled
Enable external MCP.
Configuration for a single external MCP server entry.
std::string command
Stdio command (empty for SSE)
std::vector< std::string > args
Stdio command arguments.
std::string url
SSE endpoint URL (empty for stdio)
std::unordered_map< std::string, std::string > env
Stdio environment variables.
Filesystem MCP server configuration.
bool diagnostics_on_edit
Proactive diagnostics on edit/write.
bool allow_outside_root
Allow file ops outside workspace root.
float diagnostics_timeout
Diagnostics timeout (0.1–5.0)
std::optional< int > max_read_bytes
Max file read size (nullopt = derive from context)
float max_read_context_pct
Max context % for single file read.
bool fail_on_errors
Rollback edit if it introduces errors.
Generation parameters configuration (top-level defaults).
int max_tokens
Default max tokens (64–32768)
float default_top_p
Default top_p (0.0–1.0)
float default_temperature
Default temperature (0.0–2.0)
SpeculativeConfig speculative
Speculative decoding (gh#36)
LSP integration configuration.
bool python_enabled
Enable Python LSP.
bool enabled
Enable LSP integration.
bool c_enabled
Enable C/C++ LSP.
MCP server configuration.
bool enable_entropic
Enable entropic internal server (handoff, delegate, pipeline)
FilesystemConfig filesystem
Filesystem server config.
bool enable_filesystem
Enable filesystem server.
std::unordered_map< std::string, ExternalServerEntry > external_servers
Named external servers.
bool enable_git
Enable git server.
bool enable_diagnostics
Enable diagnostics server.
int server_timeout_seconds
Server timeout (5–300)
bool enable_bash
Enable bash server.
ExternalMCPConfig external
External MCP server config (Entropic-as-server)
std::string working_dir
Server working directory (empty = CWD) (v2.0.4)
bool enable_web
Enable web server.
Model configuration for a single tier.
std::filesystem::path mmproj_path
Vision projector GGUF path.
int gpu_layers
GPU offload layers (-1 = all)
int reasoning_budget
Think token budget (-1 = unlimited)
int context_length
Context window size (512–131072)
std::filesystem::path path
Resolved model file path.
int n_threads
CPU threads (0 = auto-detect)
std::string tensor_split
Multi-GPU tensor split ratios (empty = single GPU)
std::string cache_type_k
KV cache key quantization type.
bool keep_warm
Pre-warm model at startup.
std::string cache_type_v
KV cache value quantization type.
int n_batch
Batch size for prompt processing.
bool flash_attn
Enable flash attention.
bool use_mlock
Lock model in system RAM.
std::optional< std::vector< std::string > > allowed_tools
Tool whitelist (nullopt = all)
std::string adapter
Chat adapter name.
Configuration for all models (tiers + router).
std::optional< ModelConfig > router
Router model (separate from tiers)
std::unordered_map< std::string, TierConfig > tiers
Tier name → config.
std::string default_tier
Default tier name.
Full parsed configuration.
int vram_reserve_mb
Reserved VRAM headroom (MB, 0–65536)
PermissionsConfig permissions
Tool permissions.
PromptCacheConfig prompt_cache
Prompt KV cache settings.
std::optional< std::filesystem::path > app_context
App context: nullopt = disabled by default.
CompactionConfig compaction
Auto-compaction settings.
RoutingConfig routing
Routing rules.
InferenceConfig inference
Inference-side knobs (currently speculative decoding only).
ModelsConfig models
Tiers + router.
LSPConfig lsp
LSP integration.
ConstitutionalValidationConfig constitutional_validation
Constitutional validation pipeline settings.
std::filesystem::path log_dir
Session log directory (session.log + session_model.log).
bool ggml_logging
Enable ggml/llama.cpp logging to llama_ggml.log in log_dir.
GenerationConfig generation
Default generation params.
std::string log_level
Log level string.
MCPConfig mcp
MCP server settings.
bool console_logging
Emit engine spdlog output to the stderr console sink.
bool inject_model_context
Auto-inject model context into system prompt.
bool app_context_disabled
true if app_context explicitly disabled
std::optional< std::filesystem::path > constitution
Constitution: nullopt = bundled default, disabled = explicit false.
bool constitution_disabled
true if constitution explicitly disabled
std::filesystem::path config_dir
Config dir — base for bundled data discovery.
Tool permission configuration.
std::vector< std::string > deny
Denied tool patterns (glob)
std::vector< std::string > allow
Allowed tool patterns (glob)
bool auto_approve
Skip confirmation prompts.
Prompt caching configuration.
size_t max_bytes
Maximum cache RAM (512 MB default)
bool log_hits
Log cache hit/miss at INFO level.
bool enabled
Master switch (false = no caching)
Configuration for model routing.
std::string fallback_tier
Fallback when routing fails.
std::unordered_map< std::string, std::vector< std::string > > handoff_rules
Tier handoff rules.
bool enabled
Enable routing.
std::optional< std::string > classification_prompt
Custom prompt (nullopt = auto)
std::unordered_map< std::string, std::string > tier_map
Classification → tier mapping.
Speculative-decoding configuration (inference.speculative.
bool enabled
Master switch (off by default)
int n_draft
Window size (proposed tokens)
ModelConfig draft
Full ModelConfig for the draft model.
Tier-specific model configuration.
std::optional< bool > routable
None = defer to identity frontmatter.
std::optional< std::filesystem::path > identity
Identity prompt path (nullopt = bundled)
std::optional< std::string > auto_chain
Target tier name (nullopt = defer to identity)
bool identity_disabled
true if identity explicitly disabled
std::vector< std::string > capabilities
Declared tier capabilities (gh#41).
std::optional< std::filesystem::path > grammar
Grammar file path.
Config validation functions.
ENTROPIC_EXPORT std::string validate_config(const ParsedConfig &config, std::vector< std::string > &warnings)
Validate the full ParsedConfig.
std::filesystem::path expand_home(const std::filesystem::path &p)
Expand ~ to home directory in a path.
bool extract_string_list_map(ryml::ConstNodeRef node, c4::csubstr key, std::unordered_map< std::string, std::vector< std::string > > &out)
Extract a map of string to list of strings from a YAML mapping.
std::string read_file(const std::filesystem::path &path)
Read a file into a string.
bool extract_path(ryml::ConstNodeRef node, c4::csubstr key, std::filesystem::path &out)
Extract a filesystem path with ~ expansion.
bool extract_string_list(ryml::ConstNodeRef node, c4::csubstr key, std::vector< std::string > &out)
Extract a vector of strings from a YAML sequence node.
bool extract_string_map(ryml::ConstNodeRef node, c4::csubstr key, std::unordered_map< std::string, std::string > &out)
Extract a map of string to string from a YAML mapping node.
bool extract_string_list_opt(ryml::ConstNodeRef node, c4::csubstr key, std::optional< std::vector< std::string > > &out)
Extract an optional vector of strings.
bool extract_tri_state_path(ryml::ConstNodeRef node, c4::csubstr key, std::optional< std::filesystem::path > &out, bool &disabled)
Extract a tri-state path (null = default, false = disabled, string = path).
std::string to_string(c4::csubstr s)
Convert ryml csubstr to std::string.
bool extract(ryml::ConstNodeRef node, c4::csubstr key, std::string &out)
Extract a string value from a YAML node.
ryml extraction helpers for config parsing.