13#include <nlohmann/json.hpp>
24namespace entropic::config {
106 if (!
node.has_child(
"speculative")) {
return; }
107 auto spec =
node[
"speculative"];
109 if (
extract(spec,
"mtp", mtp)) {
config.speculative_mtp = mtp; }
126 ryml::ConstNodeRef
node,
142 if (!
hf && !
hs && !
hq) {
return ""; }
147 err =
"model selector requires all of family/size/quant "
149 }
else if (
const std::string key = registry.
find_by(family, size, quant);
151 err =
"no bundled model matches family=" + family
152 +
" size=" + size +
" quant=" + quant;
178 config.gpu_layers_auto =
true;
196 ryml::ConstNodeRef
node,
201 if (!
err.empty()) {
return err; }
240 ryml::ConstNodeRef
node,
289 config.capabilities = {
"text"};
306 ryml::ConstNodeRef
node,
314 if (
node.has_child(
"router")) {
319 return "models.router: " +
err;
325 if (key ==
"default" || key ==
"router" || key ==
"defer_load") {
328 if (!
child.is_map()) {
333 if (
config.tiers.count(key) > 0) {
338 return "models." + key +
": " +
err;
355 ryml::ConstNodeRef
node,
381 ryml::ConstNodeRef
node,
392 config.warning_threshold_percent);
411 ryml::ConstNodeRef
node,
416 if (mode.empty() || mode ==
"none") {
418 }
else if (mode ==
"sandbox") {
421 return "delegation.isolation: unknown mode '" + mode +
422 "' (expected 'none' or 'sandbox')";
436 ryml::ConstNodeRef
node,
462 ryml::ConstNodeRef
node,
467 static const std::vector<std::string>
yes = {
468 "true",
"True",
"TRUE",
"yes",
"Yes",
"YES",
"on",
"On",
"ON",
"1"};
469 static const std::vector<std::string>
no = {
470 "false",
"False",
"FALSE",
"no",
"No",
"NO",
"off",
"Off",
"OFF",
473 if (std::find(
yes.begin(),
yes.end(),
raw) !=
yes.end()) {
475 }
else if (std::find(
no.begin(),
no.end(),
raw) !=
no.end()) {
477 }
else if (
raw ==
"optional") {
480 err =
"mcp.filesystem.allow_outside_root: unknown value '" +
raw
481 +
"' (expected true, false or optional)";
503 ryml::ConstNodeRef
node,
505 std::vector<std::filesystem::path>& out)
507 std::vector<std::string>
raw;
509 std::vector<std::filesystem::path>
paths;
511 for (
const auto& entry :
raw) {
513 if (!
p.is_absolute() &&
err.empty()) {
514 err =
"mcp.filesystem." +
to_string(key) +
": entry '" + entry
515 +
"' is not an absolute path";
517 paths.push_back(std::move(
p));
519 if (
err.empty()) { out = std::move(
paths); }
534 ryml::ConstNodeRef
node,
551 config.outside_root_allow);
555 config.outside_root_deny);
575 ryml::ConstNodeRef
node,
578 if (!
node.is_map()) {
599 ryml::ConstNodeRef
node,
613 if (
node.is_map() &&
node.has_child(
"socket_path")
614 && !
node[
"socket_path"].val_is_null()) {
615 std::filesystem::path
tmp;
620 if (
node.is_map() &&
node.has_child(
"tool_descriptions")) {
643 ryml::ConstNodeRef
node,
646 int timeout =
config.timeout_seconds;
648 if (
extract(
node,
"timeout_seconds", timeout) && timeout < 1) {
649 err =
"mcp.bash.timeout_seconds: must be a positive number of "
650 "seconds, got " + std::to_string(timeout);
652 config.timeout_seconds = timeout;
669 if (
node.has_child(
"env") &&
node[
"env"].is_map()) {
670 for (
auto kv :
node[
"env"]) {
671 if (
kv.has_key() &&
kv.has_val()) {
694 if (!
node.has_child(
"external_servers")) {
return ""; }
695 auto servers =
node[
"external_servers"];
696 if (!servers.is_map()) {
697 return "mcp.external_servers: must be a map of name -> server";
700 for (
auto child : servers) {
703 if (entry.command.empty() && entry.url.empty()) {
704 err =
"mcp.external_servers." + name +
": needs `command` or `url`";
707 config.external_servers[name] = std::move(entry);
722 config.enabled_mcpjson_servers);
724 config.disabled_mcpjson_servers);
726 config.enable_all_project_mcp_servers);
742 if (
config.server_timeout_seconds < 1) {
743 err =
"mcp.server_timeout_seconds: must be a positive number of "
744 "seconds, got " + std::to_string(
config.server_timeout_seconds);
763 ryml::ConstNodeRef
node,
767 if (
node.has_child(
"filesystem")) {
770 if (
err.empty() &&
node.has_child(
"bash")) {
773 if (
node.has_child(
"external")) {
792 ryml::ConstNodeRef
node,
826 ryml::ConstNodeRef
node,
849 ryml::ConstNodeRef
node,
867 ryml::ConstNodeRef
node,
889 ryml::ConstNodeRef
node,
921 ryml::ConstNodeRef
node,
932 if (
node.has_child(
"draft")) {
936 s_log->warn(
"inference.speculative.draft parse: {}",
err);
958 ryml::ConstNodeRef root,
962 if (!root.has_child(
"inference")) {
return; }
963 auto inf = root[
"inference"];
964 if (
inf.has_child(
"prompt_cache"))
966 if (
inf.has_child(
"speculative"))
968 config.inference.speculative);
982 ryml::ConstNodeRef root,
987 if (root.has_child(
"generation"))
989 if (root.has_child(
"permissions"))
991 if (root.has_child(
"mcp"))
993 if (root.has_child(
"compaction"))
995 if (root.has_child(
"lsp"))
998 if (root.has_child(
"constitutional_validation"))
1000 root[
"constitutional_validation"],
1001 config.constitutional_validation);
1016 extract(root,
"inject_model_context",
config.inject_model_context);
1018 extract(root,
"concurrent_sessions",
config.concurrent_sessions);
1033 config.app_context_content)) {
1054 const auto&
p =
config.workspace_app_context;
1058 if (!
p.empty() && (
p.is_absolute() ||
escapes)) {
1059 err =
"workspace_app_context must be a path relative to the workspace "
1060 "root with no '..' (got '" +
p.string() +
"')";
1081 ryml::ConstNodeRef root,
1088 if (
err.empty() && root.has_child(
"delegation")) {
1105 ryml::ConstNodeRef root,
1110 if (root.has_child(
"models")) {
1113 if (
err.empty() && root.has_child(
"routing")) {
1139 const std::filesystem::path& path,
1144 if (content.empty()) {
1145 return "cannot read config file: " + path.string();
1148 ryml::Tree
tree = ryml::parse_in_arena(
1149 ryml::to_csubstr(path.string()),
1150 ryml::to_csubstr(content));
1151 ryml::ConstNodeRef root =
tree.rootref();
1152 if (!root.is_map()) {
1153 return "config file root is not a YAML mapping: " + path.string();
1175 if (!std::filesystem::exists(
bundled)) {
1176 s_log->warn(
"No config found (checked global, project, bundled)");
1179 s_log->info(
"No user config — loading bundled default: {}",
1182 if (!
err.empty()) {
return "bundled default: " +
err; }
1192 std::filesystem::create_directories(
parent);
1212 const std::filesystem::path& project_path,
1220 s_log->info(
"Loading global config: {}",
global_path.string());
1222 if (!
err.empty()) {
return "global config: " +
err; }
1226 if (
err.empty() && std::filesystem::exists(project_path)) {
1227 s_log->info(
"Loading project config: {}", project_path.string());
1229 if (!
err.empty()) {
return "project config: " +
err; }
1251 const std::filesystem::path& project_path,
1256 if (!
err.empty()) {
return err; }
1262 for (
const auto&
w :
warnings) { s_log->warn(
"{}",
w); }
1277 const std::filesystem::path& path,
1291 s_log->warn(
"{}",
w);
1323 auto path = std::filesystem::path(
home) /
".entropic" /
"config.yaml";
1324 if (std::filesystem::exists(path)) {
1325 s_log->info(
"Loading global config: {}", path.string());
1328 err =
"global config: " +
err;
1368 && info.dli_fname !=
nullptr) {
1370 auto lib_path = std::filesystem::absolute(info.dli_fname,
ec);
1374 if (std::filesystem::exists(
candidate)) {
1398 if (content.empty()) {
return false; }
1399 auto tree = ryml::parse_in_arena(
1400 ryml::to_csubstr(path.string()),
1401 ryml::to_csubstr(content));
1402 auto root =
tree.rootref();
1403 return root.is_map() && root.has_child(ryml::to_csubstr(key));
1435 s_log->info(
"Consumer defines models: — replacing global tiers");
1436 config.models.tiers.clear();
1437 config.models.router.reset();
1438 config.models.default_tier.clear();
1446 s_log->warn(
"Consumer defaults failed: {}",
err);
1469 const std::filesystem::path& project_dir,
1473 if (!project_dir.empty()) {
1474 auto path = project_dir /
"config.local.yaml";
1475 if (std::filesystem::exists(path)) {
1476 s_log->info(
"Loading project config: {}", path.string());
1478 s_log->info(
"Project defines models: — replacing prior tiers");
1479 config.models.tiers.clear();
1480 config.models.router.reset();
1481 config.models.default_tier.clear();
1488 err =
"project config: " +
err;
1524 const std::filesystem::path& project_dir,
1532 if (!project_dir.empty() &&
config.log_dir.empty()) {
1533 config.log_dir = project_dir;
1537 if (
err.empty() &&
config.models.tiers.empty()) {
1582 const std::string& content,
1586 if (content.empty()) {
1587 return "config string is empty";
1590 ryml::Tree
tree = ryml::parse_in_arena(
1591 ryml::to_csubstr(
"<json>"),
1592 ryml::to_csubstr(content));
1593 auto root =
tree.rootref();
1594 if (!root.is_map()) {
1595 return "config string root is not a mapping";
1599 if (root.has_child(
"models")) {
1602 if (
err.empty() && root.has_child(
"routing")) {
1622 const std::string& content,
1636 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.
std::string find_by(const std::string &family, const std::string &size_label, const std::string &quant) const
Look up a registry key by (family, size, quant) (gh#62).
std::string err
size_exceeded, when set
const ReadWindow & w
Requested window.
static std::string validate_server_timeout(const MCPConfig &config)
Refuse a server_timeout_seconds that cannot bound anything (gh#175).
static void extract_scalar_fields(ryml::ConstNodeRef root, ParsedConfig &config)
Extract the top-level scalar/path config fields.
static std::string validate_workspace_app_context(const ParsedConfig &config)
Refuse a workspace_app_context that could leave a workspace root.
static std::string parse_outside_root_access(ryml::ConstNodeRef node, FilesystemConfig &config)
Parse mcp.filesystem.allow_outside_root (v2.13.0 tri-state).
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 ExternalServerEntry parse_external_server_entry(ryml::ConstNodeRef node)
Parse one declared external server (v2.14.0).
static std::string parse_external_servers(ryml::ConstNodeRef node, MCPConfig &config)
Parse mcp.external_servers (v2.14.0).
static std::string parse_external_mcp_config(ryml::ConstNodeRef node, ExternalMCPConfig &config)
Parse the external MCP section from a YAML node.
static void parse_mcpjson_enablement(ryml::ConstNodeRef node, MCPConfig &config)
Parse the .mcp.json enablement keys (v2.14.0, gh#199).
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)
Discover and parse .mcp.json from the project directory.
static std::string parse_generation_config(ryml::ConstNodeRef node, GenerationConfig &config)
Parse the generation section from a YAML node.
static void parse_gpu_layers(ryml::ConstNodeRef node, ModelConfig &config)
Parse gpu_layers, which may be a count or the word auto (gh#148).
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::string parse_delegation_config(ryml::ConstNodeRef node, DelegationConfig &config)
Parse the delegation section from a YAML node (gh#160).
static std::filesystem::path resolve_consumer_defaults(const std::filesystem::path &consumer_defaults)
Resolve a relative consumer_defaults path against install prefix.
static void parse_speculative_config(ryml::ConstNodeRef node, const BundledModels ®istry, SpeculativeConfig &config)
Parse inference.speculative YAML node into SpeculativeConfig.
static void parse_tool_descriptions(ryml::ConstNodeRef node, ExternalMCPConfig &config)
Parse mcp.external.tool_descriptions into the override map.
static void parse_constitutional_validation_config(ryml::ConstNodeRef node, ConstitutionalValidationConfig &config)
Parse constitutional_validation section.
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_optional_sections(ryml::ConstNodeRef root, const BundledModels ®istry, ParsedConfig &config)
Parse the optional config sections.
static void parse_tier_speculative_override(ryml::ConstNodeRef node, TierConfig &config)
Parse the per-tier speculative.mtp override (gh#108, v2.9.4).
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 std::string parse_mcp_sections(ryml::ConstNodeRef node, MCPConfig &config)
Parse the MCP section's per-server sub-sections (v2.13.0).
static std::string parse_bash_config(ryml::ConstNodeRef node, BashConfig &config)
Parse mcp.bash (v2.13.0).
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 void parse_sampler_overrides(ryml::ConstNodeRef node, TierConfig &config)
Read per-tier sampler overrides from a YAML model/tier node.
static void parse_model_runtime_knobs(ryml::ConstNodeRef node, ModelConfig &config)
Read the model-runtime knobs (KV cache, batching, threads).
static std::string parse_prompt_cache_config(ryml::ConstNodeRef node, PromptCacheConfig &config)
Parse prompt_cache config from inference YAML section.
static std::string resolve_model_path(ryml::ConstNodeRef node, const BundledModels ®istry, ModelConfig &config)
Resolve config.path from an explicit path: or a selector (gh#62).
static std::string parse_model_config(ryml::ConstNodeRef node, const BundledModels ®istry, ModelConfig &config)
Parse a ModelConfig from a YAML node.
static std::string parse_outside_root_list(ryml::ConstNodeRef node, c4::csubstr key, std::vector< std::filesystem::path > &out)
Parse one outside-root path list (outside_root_allow/_deny).
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_optional_subsections(ryml::ConstNodeRef root, const BundledModels ®istry, ParsedConfig &config)
Parse the nested optional config sub-sections.
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.
@ refuse
false: every escape refused ("Path escapes project root")
@ allow
true: every escape served, no prompt
@ optional
optional: every escape asks the host's path approver
@ tier
The TIER's identity frontmatter grammar: stem (gh#154).
@ none
Child tools share the session's working dir (default)
@ sandbox
Child tools run in a copy; output is a patch.
std::string config
–config FILE
Bash MCP server configuration (v2.13.0).
Auto-compaction configuration.
Constitutional validation pipeline configuration.
Delegation behaviour knobs (gh#160, v2.13.0).
External MCP server configuration (Entropic-as-server).
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.
Generation parameters configuration (top-level defaults).
LSP integration configuration.
MCP server configuration.
Model configuration for a single tier.
Configuration for all models (tiers + router).
Full parsed configuration.
std::filesystem::path config_dir
Config dir — base for bundled data discovery.
Tool permission configuration.
Prompt caching configuration.
Configuration for model routing.
Speculative-decoding configuration (inference.speculative.
Tier-specific model configuration.
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.
bool extract_inline_content(ryml::ConstNodeRef node, c4::csubstr key, std::optional< std::string > &out)
Extract inline text from an object-form value (gh#141).
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.