Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
loader.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
11#include "yaml_util.h"
12
13#include <nlohmann/json.hpp>
14
15#include <fstream>
16#include <optional>
17#include <sstream>
18
19#include <dlfcn.h>
20
21static auto s_log = entropic::log::get("config");
22
23namespace entropic::config {
24
34static std::string parse_model_config(
35 ryml::ConstNodeRef node,
36 const BundledModels& registry,
37 ModelConfig& config)
38{
39 std::string path_str;
40 if (extract(node, "path", path_str)) {
41 config.path = registry.resolve(path_str);
42 }
43
44 extract(node, "adapter", config.adapter);
45 extract(node, "context_length", config.context_length);
46 extract(node, "gpu_layers", config.gpu_layers);
47 extract(node, "keep_warm", config.keep_warm);
48 extract(node, "use_mlock", config.use_mlock);
49 extract(node, "reasoning_budget", config.reasoning_budget);
50 extract(node, "cache_type_k", config.cache_type_k);
51 extract(node, "cache_type_v", config.cache_type_v);
52 extract(node, "n_batch", config.n_batch);
53 extract(node, "n_threads", config.n_threads);
54 extract(node, "tensor_split", config.tensor_split);
55 extract(node, "flash_attn", config.flash_attn);
56 extract_string_list_opt(node, "allowed_tools", config.allowed_tools);
57
58 /* v1.9.11 mmproj wiring at the loader (gh#42, gh#41): read mmproj
59 * here so the YAML key matches the bundled_models.yaml registry
60 * shape and the orchestrator can declare a tier vision-capable. */
61 std::string mmproj_str;
62 if (extract(node, "mmproj", mmproj_str)) {
63 config.mmproj_path = registry.resolve(mmproj_str);
64 }
65
66 return "";
67}
68
78static std::string parse_tier_config(
79 ryml::ConstNodeRef node,
80 const BundledModels& registry,
81 TierConfig& config)
82{
83 auto err = parse_model_config(node, registry, config);
84 if (!err.empty()) {
85 return err;
86 }
87
88 extract_tri_state_path(node, "identity",
89 config.identity, config.identity_disabled);
90
91 std::string grammar_str;
92 if (extract(node, "grammar", grammar_str)) {
93 config.grammar = expand_home(std::filesystem::path(grammar_str));
94 }
95
96 std::string auto_chain_str;
97 if (extract(node, "auto_chain", auto_chain_str)) {
98 config.auto_chain = auto_chain_str;
99 }
100
101 bool routable_val = false;
102 if (extract(node, "routable", routable_val)) {
103 config.routable = routable_val;
104 }
105
106 /* gh#41 v2.1.8: tier capabilities. Missing key → ["text"] so
107 * every pre-v2.1.8 tier config remains valid. Configs that
108 * declare capabilities explicitly must include "text" themselves
109 * if the tier still serves text — we don't auto-inject. */
110 if (!extract_string_list(node, "capabilities", config.capabilities)) {
111 config.capabilities = {"text"};
112 }
113
114 return "";
115}
116
126static std::string parse_models_config(
127 ryml::ConstNodeRef node,
128 const BundledModels& registry,
129 ModelsConfig& config)
130{
131 extract(node, "default", config.default_tier);
132
133 if (node.has_child("router")) {
134 config.router.emplace();
135 auto err = parse_model_config(node["router"], registry,
136 *config.router);
137 if (!err.empty()) {
138 return "models.router: " + err;
139 }
140 }
141
142 for (auto child : node) {
143 std::string key = to_string(child.key());
144 if (key == "default" || key == "router") {
145 continue;
146 }
147 if (!child.is_map()) {
148 continue;
149 }
150
151 TierConfig tier;
152 if (config.tiers.count(key) > 0) {
153 tier = config.tiers[key];
154 }
155 auto err = parse_tier_config(child, registry, tier);
156 if (!err.empty()) {
157 return "models." + key + ": " + err;
158 }
159 config.tiers[key] = std::move(tier);
160 }
161
162 return "";
163}
164
173static std::string parse_routing_config(
174 ryml::ConstNodeRef node,
175 RoutingConfig& config)
176{
177 extract(node, "enabled", config.enabled);
178 extract(node, "fallback_tier", config.fallback_tier);
179
180 std::string class_prompt;
181 if (extract(node, "classification_prompt", class_prompt)) {
182 config.classification_prompt = class_prompt;
183 }
184
185 extract_string_map(node, "tier_map", config.tier_map);
186 extract_string_list_map(node, "handoff_rules", config.handoff_rules);
187
188 return "";
189}
190
199static std::string parse_compaction_config(
200 ryml::ConstNodeRef node,
201 CompactionConfig& config)
202{
203 extract(node, "enabled", config.enabled);
204 extract(node, "threshold_percent", config.threshold_percent);
205 extract(node, "preserve_recent_turns", config.preserve_recent_turns);
206 extract(node, "summary_max_tokens", config.summary_max_tokens);
207 extract(node, "notify_user", config.notify_user);
208 extract(node, "save_full_history", config.save_full_history);
209 extract(node, "tool_result_ttl", config.tool_result_ttl);
210 extract(node, "warning_threshold_percent",
212 return "";
213}
214
223static std::string parse_permissions_config(
224 ryml::ConstNodeRef node,
225 PermissionsConfig& config)
226{
227 extract_string_list(node, "allow", config.allow);
228 extract_string_list(node, "deny", config.deny);
229 extract(node, "auto_approve", config.auto_approve);
230 return "";
231}
232
241static std::string parse_filesystem_config(
242 ryml::ConstNodeRef node,
243 FilesystemConfig& config)
244{
245 extract(node, "diagnostics_on_edit", config.diagnostics_on_edit);
246 extract(node, "fail_on_errors", config.fail_on_errors);
247 extract(node, "diagnostics_timeout", config.diagnostics_timeout);
248 extract(node, "allow_outside_root", config.allow_outside_root);
249 extract(node, "max_read_context_pct", config.max_read_context_pct);
250
251 int max_read = 0;
252 if (extract(node, "max_read_bytes", max_read)) {
253 config.max_read_bytes = max_read;
254 }
255
256 return "";
257}
258
267static std::string parse_external_mcp_config(
268 ryml::ConstNodeRef node,
269 ExternalMCPConfig& config)
270{
271 extract(node, "enabled", config.enabled);
272 extract(node, "rate_limit", config.rate_limit);
273
274 if (node.is_map() && node.has_child("socket_path")
275 && !node["socket_path"].val_is_null()) {
276 std::filesystem::path tmp;
277 extract_path(node, "socket_path", tmp);
278 config.socket_path = tmp;
279 }
280
281 return "";
282}
283
292static std::string parse_mcp_config(
293 ryml::ConstNodeRef node,
294 MCPConfig& config)
295{
296 extract(node, "enable_entropic", config.enable_entropic);
297 extract(node, "enable_filesystem", config.enable_filesystem);
298 extract(node, "enable_bash", config.enable_bash);
299 extract(node, "enable_git", config.enable_git);
300 extract(node, "enable_diagnostics", config.enable_diagnostics);
301 extract(node, "enable_web", config.enable_web);
302 extract(node, "server_timeout_seconds", config.server_timeout_seconds);
303 extract(node, "working_dir", config.working_dir);
304
305 if (node.has_child("filesystem")) {
306 parse_filesystem_config(node["filesystem"], config.filesystem);
307 }
308 if (node.has_child("external")) {
309 parse_external_mcp_config(node["external"], config.external);
310 }
311
312 return "";
313}
314
323static std::string parse_generation_config(
324 ryml::ConstNodeRef node,
325 GenerationConfig& config)
326{
327 extract(node, "max_tokens", config.max_tokens);
328 extract(node, "default_temperature", config.default_temperature);
329 extract(node, "default_top_p", config.default_top_p);
330 return "";
331}
332
341static std::string parse_lsp_config(
342 ryml::ConstNodeRef node,
343 LSPConfig& config)
344{
345 extract(node, "enabled", config.enabled);
346 extract(node, "python_enabled", config.python_enabled);
347 extract(node, "c_enabled", config.c_enabled);
348 return "";
349}
350
359static std::string parse_prompt_cache_config(
360 ryml::ConstNodeRef node,
361 PromptCacheConfig& config)
362{
363 extract(node, "enabled", config.enabled);
364 extract(node, "log_hits", config.log_hits);
365
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);
369 }
370
371 return "";
372}
373
382 ryml::ConstNodeRef node,
384{
385 extract(node, "enabled", config.enabled);
386 extract(node, "max_revisions", config.max_revisions);
387 extract(node, "max_critique_tokens", config.max_critique_tokens);
388 extract(node, "temperature", config.temperature);
389 extract(node, "enable_thinking", config.enable_thinking);
390 extract(node, "priority", config.priority);
391 extract(node, "grammar_key", config.grammar_key);
392 extract_string_list(node, "skip_tiers", config.skip_tiers);
393}
394
413 ryml::ConstNodeRef node,
414 const BundledModels& registry,
415 SpeculativeConfig& config)
416{
417 extract(node, "enabled", config.enabled);
418 extract(node, "n_draft", config.n_draft);
419 // Nested ModelConfig — every llama.cpp knob is consumer-tunable
420 // via `inference.speculative.draft.<field>`. Defaults come from
421 // `make_default_draft_model_config()` (gpu_layers=0, flash_attn=
422 // false, context_length=8192, n_threads=4). (v2.1.11)
423 if (node.has_child("draft")) {
424 auto err = parse_model_config(
425 node["draft"], registry, config.draft);
426 if (!err.empty()) {
427 s_log->warn("inference.speculative.draft parse: {}", err);
428 }
429 }
430}
431
449 ryml::ConstNodeRef root,
450 const BundledModels& registry,
451 ParsedConfig& config)
452{
453 if (!root.has_child("inference")) { return; }
454 auto inf = root["inference"];
455 if (inf.has_child("prompt_cache"))
456 parse_prompt_cache_config(inf["prompt_cache"], config.prompt_cache);
457 if (inf.has_child("speculative"))
458 parse_speculative_config(inf["speculative"], registry,
459 config.inference.speculative);
460}
461
471 ryml::ConstNodeRef root,
472 const BundledModels& registry,
473 ParsedConfig& config)
474{
475 if (root.has_child("generation"))
476 parse_generation_config(root["generation"], config.generation);
477 if (root.has_child("permissions"))
478 parse_permissions_config(root["permissions"], config.permissions);
479 if (root.has_child("mcp"))
480 parse_mcp_config(root["mcp"], config.mcp);
481 if (root.has_child("compaction"))
482 parse_compaction_config(root["compaction"], config.compaction);
483 if (root.has_child("lsp"))
484 parse_lsp_config(root["lsp"], config.lsp);
485 parse_inference_subsections(root, registry, config);
486 if (root.has_child("constitutional_validation"))
488 root["constitutional_validation"],
490}
491
499static void extract_scalar_fields(ryml::ConstNodeRef root,
500 ParsedConfig& config)
501{
502 extract(root, "log_level", config.log_level);
503 extract(root, "inject_model_context", config.inject_model_context);
504 extract(root, "vram_reserve_mb", config.vram_reserve_mb);
505 extract_path(root, "config_dir", config.config_dir);
506 extract_path(root, "log_dir", config.log_dir);
507 extract(root, "ggml_logging", config.ggml_logging);
508 extract(root, "console_logging", config.console_logging);
509
510 extract_tri_state_path(root, "constitution",
511 config.constitution, config.constitution_disabled);
512 extract_tri_state_path(root, "app_context",
513 config.app_context, config.app_context_disabled);
514}
515
524 ryml::ConstNodeRef root,
525 const BundledModels& registry,
526 ParsedConfig& config)
527{
528 parse_optional_subsections(root, registry, config);
529 extract_scalar_fields(root, config);
530}
531
541static std::string parse_top_sections(
542 ryml::ConstNodeRef root,
543 const BundledModels& registry,
544 ParsedConfig& config)
545{
546 std::string err;
547 if (root.has_child("models")) {
548 err = parse_models_config(root["models"], registry, config.models);
549 }
550 if (err.empty() && root.has_child("routing")) {
551 err = parse_routing_config(root["routing"], config.routing);
552 }
553 if (err.empty()) {
554 parse_optional_sections(root, registry, config);
555 }
556 return err;
557}
558
574 const std::filesystem::path& path,
575 const BundledModels& registry,
576 ParsedConfig& config)
577{
578 auto content = read_file(path);
579 if (content.empty()) {
580 return "cannot read config file: " + path.string();
581 }
582
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();
589 }
590
591 return parse_top_sections(root, registry, config);
592}
593
603static std::string load_bundled_default(
604 const std::filesystem::path& global_path,
605 const BundledModels& registry,
606 ParsedConfig& config)
607{
608 auto data_dir = resolve_data_dir(config);
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)");
612 return "";
613 }
614 s_log->info("No user config — loading bundled default: {}",
615 bundled.string());
616 auto err = parse_config_file(bundled, registry, config);
617 if (!err.empty()) { return "bundled default: " + err; }
618
619 // Auto-create global config so this only happens once
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());
625 }
626 return "";
627}
628
639static std::string load_config_layers(
640 const std::filesystem::path& global_path,
641 const std::filesystem::path& project_path,
642 const BundledModels& registry,
643 ParsedConfig& config)
644{
645 bool have_config = false;
646 std::string err;
647
648 if (std::filesystem::exists(global_path)) {
649 s_log->info("Loading global config: {}", global_path.string());
650 err = parse_config_file(global_path, registry, config);
651 if (!err.empty()) { return "global config: " + err; }
652 have_config = true;
653 }
654
655 if (err.empty() && std::filesystem::exists(project_path)) {
656 s_log->info("Loading project config: {}", project_path.string());
657 err = parse_config_file(project_path, registry, config);
658 if (!err.empty()) { return "project config: " + err; }
659 have_config = true;
660 }
661
662 return have_config ? ""
663 : load_bundled_default(global_path, registry, config);
664}
665
676std::string load_config(
677 const std::filesystem::path& global_path,
678 const std::filesystem::path& project_path,
679 const BundledModels& registry,
680 ParsedConfig& config)
681{
682 auto err = load_config_layers(global_path, project_path, registry, config);
683 if (!err.empty()) { return err; }
684
685 apply_env_overrides(config);
686
687 std::vector<std::string> warnings;
688 err = validate_config(config, warnings);
689 for (const auto& w : warnings) { s_log->warn("{}", w); }
690 return err;
691}
692
703 const std::filesystem::path& path,
704 const BundledModels& registry,
705 ParsedConfig& config)
706{
707 auto err = parse_config_file(path, registry, config);
708 if (!err.empty()) {
709 return err;
710 }
711
712 apply_env_overrides(config);
713
714 std::vector<std::string> warnings;
715 err = validate_config(config, warnings);
716 for (const auto& w : warnings) {
717 s_log->warn("{}", w);
718 }
719 return err;
720}
721
731 const std::string& name,
732 const nlohmann::json& entry) {
733 ExternalServerEntry result;
734 std::string type = entry.value("type", std::string("stdio"));
735 if (type == "sse") {
736 result.url = entry.value("url", std::string{});
737 } else {
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>());
742 }
743 }
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>();
748 }
749 }
750 }
751 s_log->info("Discovered external MCP server: {} (type={})",
752 name, type);
753 return result;
754}
755
778static std::optional<nlohmann::json> read_mcp_servers(
779 const std::filesystem::path& path) {
780 std::ifstream f(path);
781 nlohmann::json j;
782 if (f.is_open()) {
783 std::stringstream ss;
784 ss << f.rdbuf();
785 j = nlohmann::json::parse(ss.str(), nullptr, false);
786 }
787 bool valid = f.is_open() && !j.is_discarded() && j.is_object()
788 && j.contains("mcpServers")
789 && j["mcpServers"].is_object();
790 if (!valid) {
791 s_log->warn(".mcp.json missing/malformed: {}", path.string());
792 return std::nullopt;
793 }
794 return j["mcpServers"];
795}
796
803 const std::filesystem::path& project_dir,
804 ParsedConfig& config) {
805 if (project_dir.empty()) { return; }
806 auto path = project_dir / ".mcp.json";
807 if (!std::filesystem::exists(path)) { return; }
808 auto servers = read_mcp_servers(path);
809 if (!servers) { return; }
810
811 int added = 0;
812 for (auto it = servers->begin(); it != servers->end(); ++it) {
813 const std::string& name = it.key();
814 if (config.mcp.external_servers.count(name)) {
815 s_log->info(".mcp.json: {} already in config, skipping", name);
816 continue;
817 }
819 name, it.value());
820 added++;
821 }
822 s_log->info("Loaded {} external MCP server(s) from {}",
823 added, path.string());
824}
825
832static std::string load_global_layer(
833 const BundledModels& registry, ParsedConfig& config)
834{
835 const char* home = getenv("HOME");
836 std::string err;
837 if (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());
841 err = parse_config_file(path, registry, config);
842 if (!err.empty()) {
843 err = "global config: " + err;
844 }
845 }
846 }
847 return err;
848}
849
868static std::filesystem::path resolve_consumer_defaults(
869 const std::filesystem::path& consumer_defaults)
870{
871 // Input already usable (empty, absolute, or found at CWD) → passthrough.
872 if (consumer_defaults.empty()
873 || consumer_defaults.is_absolute()
874 || std::filesystem::exists(consumer_defaults)) {
875 return consumer_defaults;
876 }
877 // Fall back to <install-prefix>/share/entropic/<filename>. Uses
878 // the same librentropic.so location trick — dladdr on any address
879 // in this translation unit resolves to the .so's on-disk path.
880 std::filesystem::path result = consumer_defaults;
881 Dl_info info = {};
882 if (dladdr(reinterpret_cast<void*>(&resolve_consumer_defaults), &info) != 0
883 && info.dli_fname != nullptr) {
884 std::error_code ec;
885 auto lib_path = std::filesystem::absolute(info.dli_fname, ec);
886 if (!ec) {
887 auto candidate = lib_path.parent_path().parent_path()
888 / "share" / "entropic" / consumer_defaults.filename();
889 if (std::filesystem::exists(candidate)) {
890 result = candidate;
891 }
892 }
893 }
894 return result;
895}
896
910static bool yaml_has_key(const std::filesystem::path& path,
911 const char* key) {
912 auto content = read_file(path);
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));
919}
920
936 const std::filesystem::path& consumer_defaults_in,
937 const BundledModels& registry, ParsedConfig& config)
938{
939 auto consumer_defaults = resolve_consumer_defaults(consumer_defaults_in);
940 if (consumer_defaults.empty() || !std::filesystem::exists(consumer_defaults)) {
941 return;
942 }
943 s_log->info("Loading consumer defaults: {}", consumer_defaults.string());
944
945 // Replace semantics: if consumer defines models or routing,
946 // clear existing tiers/routing so global ones don't leak through.
947 if (yaml_has_key(consumer_defaults, "models")) {
948 s_log->info("Consumer defines models: — replacing global tiers");
949 config.models.tiers.clear();
950 config.models.router.reset();
951 config.models.default_tier.clear();
952 }
953 if (yaml_has_key(consumer_defaults, "routing")) {
954 config.routing = RoutingConfig{};
955 }
956
957 auto err = parse_config_file(consumer_defaults, registry, config);
958 if (!err.empty()) {
959 s_log->warn("Consumer defaults failed: {}", err);
960 }
961}
962
980static std::string load_project_layer(
981 const std::filesystem::path& project_dir,
982 const BundledModels& registry, ParsedConfig& config)
983{
984 std::string err;
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());
989 if (yaml_has_key(path, "models")) {
990 s_log->info("Project defines models: — replacing prior tiers");
991 config.models.tiers.clear();
992 config.models.router.reset();
993 config.models.default_tier.clear();
994 }
995 if (yaml_has_key(path, "routing")) {
996 config.routing = RoutingConfig{};
997 }
998 err = parse_config_file(path, registry, config);
999 if (!err.empty()) {
1000 err = "project config: " + err;
1001 }
1002 }
1003 }
1004 return err;
1005}
1006
1033std::string load_layered(
1034 const std::filesystem::path& project_dir,
1035 const std::filesystem::path& consumer_defaults,
1036 const BundledModels& registry,
1037 ParsedConfig& config)
1038{
1039 auto err = load_global_layer(registry, config);
1040 if (err.empty()) {
1041 load_consumer_layer(consumer_defaults, registry, config);
1042 if (!project_dir.empty() && config.log_dir.empty()) {
1043 config.log_dir = project_dir;
1044 }
1045 err = load_project_layer(project_dir, registry, config);
1046 }
1047 if (err.empty() && config.models.tiers.empty()) {
1048 err = load_bundled_default(std::filesystem::path{}, registry, config);
1049 }
1050 if (err.empty()) {
1051 apply_env_overrides(config);
1052 discover_mcp_json(project_dir, config);
1053 }
1054 return err;
1055}
1056
1070static std::string parse_config_string(
1071 const std::string& content,
1072 const BundledModels& registry,
1073 ParsedConfig& config)
1074{
1075 if (content.empty()) {
1076 return "config string is empty";
1077 }
1078
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";
1085 }
1086
1087 std::string err;
1088 if (root.has_child("models")) {
1089 err = parse_models_config(root["models"], registry, config.models);
1090 }
1091 if (err.empty() && root.has_child("routing")) {
1092 err = parse_routing_config(root["routing"], config.routing);
1093 }
1094 if (err.empty()) {
1095 parse_optional_sections(root, registry, config);
1096 }
1097 return err;
1098}
1099
1110 const std::string& content,
1111 const BundledModels& registry,
1112 ParsedConfig& config)
1113{
1114 auto err = parse_config_string(content, registry, config);
1115 if (!err.empty()) {
1116 return err;
1117 }
1118
1119 apply_env_overrides(config);
1120
1121 std::vector<std::string> warnings;
1122 err = validate_config(config, warnings);
1123 for (const auto& w : warnings) {
1124 s_log->warn("{}", w);
1125 }
1126 return err;
1127}
1128
1129} // namespace entropic::config
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.
Definition loader.cpp:499
static std::string parse_config_string(const std::string &content, const BundledModels &registry, ParsedConfig &config)
Parse a config string (YAML or JSON) and overlay onto config.
Definition loader.cpp:1070
static std::string load_project_layer(const std::filesystem::path &project_dir, const BundledModels &registry, ParsedConfig &config)
Load the project-local layer if present.
Definition loader.cpp:980
static std::string parse_external_mcp_config(ryml::ConstNodeRef node, ExternalMCPConfig &config)
Parse the external MCP section from a YAML node.
Definition loader.cpp:267
static bool yaml_has_key(const std::filesystem::path &path, const char *key)
Load the consumer/app defaults layer if present (non-fatal).
Definition loader.cpp:910
static std::string load_global_layer(const BundledModels &registry, ParsedConfig &config)
Load the global user config layer if present.
Definition loader.cpp:832
static std::string parse_generation_config(ryml::ConstNodeRef node, GenerationConfig &config)
Parse the generation section from a YAML node.
Definition loader.cpp:323
static std::string parse_permissions_config(ryml::ConstNodeRef node, PermissionsConfig &config)
Parse the permissions section from a YAML node.
Definition loader.cpp:223
static void parse_inference_subsections(ryml::ConstNodeRef root, const BundledModels &registry, ParsedConfig &config)
Parse the nested optional config sub-sections.
Definition loader.cpp:448
static void load_consumer_layer(const std::filesystem::path &consumer_defaults_in, const BundledModels &registry, ParsedConfig &config)
Load the consumer-defaults layer.
Definition loader.cpp:935
static std::filesystem::path resolve_consumer_defaults(const std::filesystem::path &consumer_defaults)
Resolve a relative consumer_defaults path against install prefix.
Definition loader.cpp:868
static std::optional< nlohmann::json > read_mcp_servers(const std::filesystem::path &path)
Discover and parse .mcp.json from the project directory.
Definition loader.cpp:778
static void parse_optional_sections(ryml::ConstNodeRef root, const BundledModels &registry, ParsedConfig &config)
Parse optional config sections that don't return errors.
Definition loader.cpp:523
static void parse_speculative_config(ryml::ConstNodeRef node, const BundledModels &registry, SpeculativeConfig &config)
Parse inference.speculative YAML node into SpeculativeConfig.
Definition loader.cpp:412
static void parse_constitutional_validation_config(ryml::ConstNodeRef node, ConstitutionalValidationConfig &config)
Parse constitutional_validation section.
Definition loader.cpp:381
static void parse_optional_subsections(ryml::ConstNodeRef root, const BundledModels &registry, ParsedConfig &config)
Parse the nested optional config sub-sections.
Definition loader.cpp:470
static std::string parse_mcp_config(ryml::ConstNodeRef node, MCPConfig &config)
Parse the MCP section from a YAML node.
Definition loader.cpp:292
static std::string load_bundled_default(const std::filesystem::path &global_path, const BundledModels &registry, ParsedConfig &config)
Load bundled default config when no user config exists.
Definition loader.cpp:603
static std::string parse_filesystem_config(ryml::ConstNodeRef node, FilesystemConfig &config)
Parse the filesystem section from a YAML node.
Definition loader.cpp:241
static std::string parse_compaction_config(ryml::ConstNodeRef node, CompactionConfig &config)
Parse the compaction section from a YAML node.
Definition loader.cpp:199
static void discover_mcp_json(const std::filesystem::path &project_dir, ParsedConfig &config)
Discover + merge external MCP servers from <dir>/.mcp.json.
Definition loader.cpp:802
static std::string parse_top_sections(ryml::ConstNodeRef root, const BundledModels &registry, ParsedConfig &config)
Parse the top-level models/routing/optional sections.
Definition loader.cpp:541
static std::string parse_routing_config(ryml::ConstNodeRef node, RoutingConfig &config)
Parse the routing section from a YAML node.
Definition loader.cpp:173
static std::string parse_prompt_cache_config(ryml::ConstNodeRef node, PromptCacheConfig &config)
Parse prompt_cache config from inference YAML section.
Definition loader.cpp:359
static std::string parse_model_config(ryml::ConstNodeRef node, const BundledModels &registry, ModelConfig &config)
Parse a ModelConfig from a YAML node.
Definition loader.cpp:34
static ExternalServerEntry parse_mcp_json_entry(const std::string &name, const nlohmann::json &entry)
Parse a single mcpServers entry from .mcp.json.
Definition loader.cpp:730
static std::string load_config_layers(const std::filesystem::path &global_path, const std::filesystem::path &project_path, const BundledModels &registry, ParsedConfig &config)
Load global, project, and bundled config layers in order.
Definition loader.cpp:639
static std::string parse_tier_config(ryml::ConstNodeRef node, const BundledModels &registry, TierConfig &config)
Parse a TierConfig from a YAML node.
Definition loader.cpp:78
static std::string parse_models_config(ryml::ConstNodeRef node, const BundledModels &registry, ModelsConfig &config)
Parse the models section from a YAML node.
Definition loader.cpp:126
static std::string parse_lsp_config(ryml::ConstNodeRef node, LSPConfig &config)
Parse the LSP section from a YAML node.
Definition loader.cpp:341
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 &registry, ParsedConfig &config)
Load config using layered resolution.
Definition loader.cpp:676
ENTROPIC_EXPORT std::string load_config_from_string(const std::string &content, const BundledModels &registry, ParsedConfig &config)
Load config from a YAML/JSON string (no layering).
Definition loader.cpp:1109
ENTROPIC_EXPORT std::filesystem::path resolve_data_dir(const ParsedConfig &config)
Resolve the bundled data directory.
Definition data_dir.cpp:81
ENTROPIC_EXPORT std::string parse_config_file(const std::filesystem::path &path, const BundledModels &registry, ParsedConfig &config)
Parse a config YAML file and overlay onto existing config.
Definition loader.cpp:573
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 &registry, ParsedConfig &config)
Load config from a single YAML file (no layering).
Definition loader.cpp:702
ENTROPIC_EXPORT std::string load_layered(const std::filesystem::path &project_dir, const std::filesystem::path &consumer_defaults, const BundledModels &registry, ParsedConfig &config)
Load config with consumer defaults + global + project layers.
Definition loader.cpp:1033
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Definition logging.cpp:211
Auto-compaction configuration.
Definition config.h:508
bool save_full_history
Save full history before compaction.
Definition config.h:514
bool notify_user
Notify user on compaction.
Definition config.h:513
float warning_threshold_percent
Warning trigger (0.3–0.9)
Definition config.h:516
int preserve_recent_turns
Turns to preserve (1–10)
Definition config.h:511
int summary_max_tokens
Summary max tokens (500–4000)
Definition config.h:512
int tool_result_ttl
Tool result TTL in turns (>= 1; v2.1.3 #6: gated on fill, no upper bound)
Definition config.h:515
float threshold_percent
Compaction trigger (0.5–0.99)
Definition config.h:510
bool enabled
Enable auto-compaction.
Definition config.h:509
Constitutional validation pipeline configuration.
Definition config.h:565
int max_revisions
Max re-generation attempts (0 = critique only)
Definition config.h:567
int priority
Hook priority (higher = later)
Definition config.h:571
bool enable_thinking
Enable think-blocks for critique (default OFF)
Definition config.h:570
float temperature
Critique generation temperature.
Definition config.h:569
bool enabled
Global enable/disable (default OFF)
Definition config.h:566
int max_critique_tokens
Token budget for critique generation.
Definition config.h:568
std::string grammar_key
Grammar registry key.
Definition config.h:572
std::vector< std::string > skip_tiers
Tiers exempt from validation (default: lead — streams before hook fires)
Definition config.h:574
External MCP server configuration (Entropic-as-server).
Definition config.h:423
std::optional< std::filesystem::path > socket_path
Socket path (nullopt = derived)
Definition config.h:425
int rate_limit
Requests per minute (1–100)
Definition config.h:426
bool enabled
Enable external MCP.
Definition config.h:424
Configuration for a single external MCP server entry.
Definition config.h:444
std::string command
Stdio command (empty for SSE)
Definition config.h:445
std::vector< std::string > args
Stdio command arguments.
Definition config.h:446
std::string url
SSE endpoint URL (empty for stdio)
Definition config.h:448
std::unordered_map< std::string, std::string > env
Stdio environment variables.
Definition config.h:447
Filesystem MCP server configuration.
Definition config.h:410
bool diagnostics_on_edit
Proactive diagnostics on edit/write.
Definition config.h:411
bool allow_outside_root
Allow file ops outside workspace root.
Definition config.h:414
float diagnostics_timeout
Diagnostics timeout (0.1–5.0)
Definition config.h:413
std::optional< int > max_read_bytes
Max file read size (nullopt = derive from context)
Definition config.h:415
float max_read_context_pct
Max context % for single file read.
Definition config.h:416
bool fail_on_errors
Rollback edit if it introduces errors.
Definition config.h:412
Generation parameters configuration (top-level defaults).
Definition config.h:523
int max_tokens
Default max tokens (64–32768)
Definition config.h:524
float default_top_p
Default top_p (0.0–1.0)
Definition config.h:526
float default_temperature
Default temperature (0.0–2.0)
Definition config.h:525
SpeculativeConfig speculative
Speculative decoding (gh#36)
Definition config.h:703
LSP integration configuration.
Definition config.h:543
bool python_enabled
Enable Python LSP.
Definition config.h:545
bool enabled
Enable LSP integration.
Definition config.h:544
bool c_enabled
Enable C/C++ LSP.
Definition config.h:546
MCP server configuration.
Definition config.h:455
bool enable_entropic
Enable entropic internal server (handoff, delegate, pipeline)
Definition config.h:456
FilesystemConfig filesystem
Filesystem server config.
Definition config.h:462
bool enable_filesystem
Enable filesystem server.
Definition config.h:457
std::unordered_map< std::string, ExternalServerEntry > external_servers
Named external servers.
Definition config.h:468
bool enable_git
Enable git server.
Definition config.h:459
bool enable_diagnostics
Enable diagnostics server.
Definition config.h:460
int server_timeout_seconds
Server timeout (5–300)
Definition config.h:464
bool enable_bash
Enable bash server.
Definition config.h:458
ExternalMCPConfig external
External MCP server config (Entropic-as-server)
Definition config.h:463
std::string working_dir
Server working directory (empty = CWD) (v2.0.4)
Definition config.h:465
bool enable_web
Enable web server.
Definition config.h:461
Model configuration for a single tier.
Definition config.h:148
std::filesystem::path mmproj_path
Vision projector GGUF path.
Definition config.h:174
int gpu_layers
GPU offload layers (-1 = all)
Definition config.h:152
int reasoning_budget
Think token budget (-1 = unlimited)
Definition config.h:157
int context_length
Context window size (512–131072)
Definition config.h:151
std::filesystem::path path
Resolved model file path.
Definition config.h:149
int n_threads
CPU threads (0 = auto-detect)
Definition config.h:161
std::string tensor_split
Multi-GPU tensor split ratios (empty = single GPU)
Definition config.h:162
std::string cache_type_k
KV cache key quantization type.
Definition config.h:158
bool keep_warm
Pre-warm model at startup.
Definition config.h:153
std::string cache_type_v
KV cache value quantization type.
Definition config.h:159
int n_batch
Batch size for prompt processing.
Definition config.h:160
bool flash_attn
Enable flash attention.
Definition config.h:163
bool use_mlock
Lock model in system RAM.
Definition config.h:154
std::optional< std::vector< std::string > > allowed_tools
Tool whitelist (nullopt = all)
Definition config.h:166
std::string adapter
Chat adapter name.
Definition config.h:150
Configuration for all models (tiers + router).
Definition config.h:356
std::optional< ModelConfig > router
Router model (separate from tiers)
Definition config.h:358
std::unordered_map< std::string, TierConfig > tiers
Tier name → config.
Definition config.h:357
std::string default_tier
Default tier name.
Definition config.h:359
Full parsed configuration.
Definition config.h:714
int vram_reserve_mb
Reserved VRAM headroom (MB, 0–65536)
Definition config.h:735
PermissionsConfig permissions
Tool permissions.
Definition config.h:718
PromptCacheConfig prompt_cache
Prompt KV cache settings.
Definition config.h:722
std::optional< std::filesystem::path > app_context
App context: nullopt = disabled by default.
Definition config.h:731
CompactionConfig compaction
Auto-compaction settings.
Definition config.h:720
RoutingConfig routing
Routing rules.
Definition config.h:716
InferenceConfig inference
Inference-side knobs (currently speculative decoding only).
Definition config.h:760
ModelsConfig models
Tiers + router.
Definition config.h:715
LSPConfig lsp
LSP integration.
Definition config.h:721
ConstitutionalValidationConfig constitutional_validation
Constitutional validation pipeline settings.
Definition config.h:756
std::filesystem::path log_dir
Session log directory (session.log + session_model.log).
Definition config.h:742
bool ggml_logging
Enable ggml/llama.cpp logging to llama_ggml.log in log_dir.
Definition config.h:746
GenerationConfig generation
Default generation params.
Definition config.h:717
std::string log_level
Log level string.
Definition config.h:724
MCPConfig mcp
MCP server settings.
Definition config.h:719
bool console_logging
Emit engine spdlog output to the stderr console sink.
Definition config.h:753
bool inject_model_context
Auto-inject model context into system prompt.
Definition config.h:734
bool app_context_disabled
true if app_context explicitly disabled
Definition config.h:732
std::optional< std::filesystem::path > constitution
Constitution: nullopt = bundled default, disabled = explicit false.
Definition config.h:727
bool constitution_disabled
true if constitution explicitly disabled
Definition config.h:728
std::filesystem::path config_dir
Config dir — base for bundled data discovery.
Definition config.h:738
Tool permission configuration.
Definition config.h:400
std::vector< std::string > deny
Denied tool patterns (glob)
Definition config.h:402
std::vector< std::string > allow
Allowed tool patterns (glob)
Definition config.h:401
bool auto_approve
Skip confirmation prompts.
Definition config.h:403
Prompt caching configuration.
Definition config.h:196
size_t max_bytes
Maximum cache RAM (512 MB default)
Definition config.h:197
bool log_hits
Log cache hit/miss at INFO level.
Definition config.h:199
bool enabled
Master switch (false = no caching)
Definition config.h:198
Configuration for model routing.
Definition config.h:388
std::string fallback_tier
Fallback when routing fails.
Definition config.h:390
std::unordered_map< std::string, std::vector< std::string > > handoff_rules
Tier handoff rules.
Definition config.h:393
bool enabled
Enable routing.
Definition config.h:389
std::optional< std::string > classification_prompt
Custom prompt (nullopt = auto)
Definition config.h:391
std::unordered_map< std::string, std::string > tier_map
Classification → tier mapping.
Definition config.h:392
Speculative-decoding configuration (inference.speculative.
Definition config.h:672
bool enabled
Master switch (off by default)
Definition config.h:673
int n_draft
Window size (proposed tokens)
Definition config.h:674
ModelConfig draft
Full ModelConfig for the draft model.
Definition config.h:690
Tier-specific model configuration.
Definition config.h:286
std::optional< bool > routable
None = defer to identity frontmatter.
Definition config.h:291
std::optional< std::filesystem::path > identity
Identity prompt path (nullopt = bundled)
Definition config.h:287
std::optional< std::string > auto_chain
Target tier name (nullopt = defer to identity)
Definition config.h:290
bool identity_disabled
true if identity explicitly disabled
Definition config.h:288
std::vector< std::string > capabilities
Declared tier capabilities (gh#41).
Definition config.h:316
std::optional< std::filesystem::path > grammar
Grammar file path.
Definition config.h:289
Config validation functions.
ENTROPIC_EXPORT std::string validate_config(const ParsedConfig &config, std::vector< std::string > &warnings)
Validate the full ParsedConfig.
Definition validate.cpp:296
std::filesystem::path expand_home(const std::filesystem::path &p)
Expand ~ to home directory in a path.
Definition yaml_util.cpp:61
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.
Definition yaml_util.cpp:39
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.
Definition yaml_util.cpp:27
bool extract(ryml::ConstNodeRef node, c4::csubstr key, std::string &out)
Extract a string value from a YAML node.
Definition yaml_util.cpp:85
ryml extraction helpers for config parsing.