Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
entropic.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
13#include "engine_handle.h"
14
17#include <entropic/entropic.h>
20#include "llama_cpp_backend.h"
25#include "json_serializers.h"
26#include "utf8_safe.h"
27#include <cstdlib>
28#include <cstring>
29#include <new>
30#include <stdexcept>
31#include <string>
32
33static auto s_log = entropic::log::get("facade");
34
46static char* alloc_cstr(const char* src) {
47 if (!src) { return nullptr; }
48 size_t len = std::strlen(src) + 1;
49 auto* dst = static_cast<char*>(entropic_alloc(len));
50 if (dst) { std::memcpy(dst, src, len); }
51 return dst;
52}
53
61static char* alloc_cstr(const std::string& src) {
62 return alloc_cstr(src.c_str());
63}
64
65/* setup_ggml_logging moved to ModelOrchestrator::initialize() — Step 7 */
66
67// gh#58 follow-up (v2.2.6): per-handle last_error. Pre-v2.2.6 the
68// public API in src/types/error.cpp ignored the handle parameter and
69// returned a single thread-local buffer, so every handle->last_error
70// assignment in the facade was unreadable from the consumer side.
71// Thread-local cache here means the returned const char* stays valid
72// until the *same* thread calls entropic_last_error again — matching
73// the documented v1.8.0 contract.
74static thread_local std::string s_last_error_cache;
75static thread_local char s_pre_create_error[512] = "";
76
90extern "C" const char* entropic_last_error(entropic_handle_t handle) {
91 if (!handle) { return s_pre_create_error; }
92 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
93 s_last_error_cache = handle->last_error;
94 return s_last_error_cache.c_str();
95}
96
105 if (!h) { return ENTROPIC_ERROR_INVALID_HANDLE; }
106 if (!h->configured.load() || !h->orchestrator) {
108 }
109 return ENTROPIC_OK;
110}
111
120 if (!h) { return ENTROPIC_ERROR_INVALID_HANDLE; }
121 if (!h->configured.load() || !h->mcp_auth) {
123 }
124 return ENTROPIC_OK;
125}
126
135 if (!h) { return ENTROPIC_ERROR_INVALID_HANDLE; }
136 if (!h->configured.load() || !h->identity_manager) {
138 }
139 return ENTROPIC_OK;
140}
141
142/* find_tier_by_model_path moved to ModelsConfig::find_tier_by_path() — v2.0.1 */
143
154 entropic_handle_t h, const char* tier_name)
155{
156 auto* backend = h->orchestrator->get_backend(tier_name);
157 if (!backend) {
158 throw std::runtime_error(
159 "no backend for tier: " + std::string(tier_name));
160 }
161 if (!backend->is_active()) {
162 throw std::runtime_error(
163 "model not active: " + std::string(tier_name));
164 }
165 return backend;
166}
167
168extern "C" {
169
182 if (handle == nullptr) {
184 }
185 entropic::log::init(spdlog::level::info);
186 entropic_inference_log_silence(); // silent until configure enables
187
188 auto* engine = new (std::nothrow) entropic_engine();
189 if (engine == nullptr) {
190 *handle = nullptr;
192 }
193
194 // gh#59 (v2.3.1): assign a monotonic log id. Used by the
195 // HandleAwareSink dispatcher to route session.log writes to the
196 // right file when multiple handles coexist. 0 is reserved for
197 // "no handle scope active."
198 static std::atomic<int> s_log_id_counter{0};
199 engine->log_id = ++s_log_id_counter;
200
201 s_log->info("entropic_create() — v{} (log_id={})",
202 CONFIG_ENTROPIC_VERSION_STRING, engine->log_id);
203
204 *handle = engine;
205 return ENTROPIC_OK;
206}
207
226/* preload_bundled_models moved to BundledModels::auto_discover_and_load() — Step 6 */
227
228/* InferenceInterface wiring moved to inference/interface_factory.cpp — Step 3 */
229
230/* InferenceInterface C-wrappers + factory moved to inference/interface_factory.cpp — Step 3 */
231
232/* facade_process_directives → engine->build_directive_hooks() — v2.0.2 */
233/* facade_resolve_tier/tier_exists/handoff/param → engine->set_tier_info() — v2.0.2 */
234/* wire_engine_interfaces → inline in configure_common — v2.0.2 */
235
236// ── Tool prompt injection (v2.0.4) ─────────────────────────
237
250static std::vector<std::string> resolve_allowed_tools(
251 entropic_engine* h, const std::string& tier) {
252 // Primary: cached from identity frontmatter (v2.0.4)
253 auto cached = h->tier_allowed_tools.find(tier);
254 if (cached != h->tier_allowed_tools.end()) {
255 return cached->second;
256 }
257 // Fallback: dynamic identity or model config
258 if (h->identity_manager) {
259 auto* cfg = h->identity_manager->get(tier);
260 if (cfg && !cfg->allowed_tools.empty()) {
261 return cfg->allowed_tools;
262 }
263 }
264 return {};
265}
266
276static std::vector<std::string> filter_tools(
277 const nlohmann::json& all_tools,
278 const std::vector<std::string>& allowed) {
279 std::vector<std::string> result;
280 for (const auto& tool : all_tools) {
281 std::string name = tool.value("name", "");
282 bool pass = allowed.empty()
283 || std::find(allowed.begin(), allowed.end(), name)
284 != allowed.end();
285 if (pass) { result.push_back(tool.dump()); }
286 }
287 return result;
288}
289
300static int facade_get_tool_prompt(const char* tier, char** result,
301 void* user_data) {
302 auto* h = static_cast<entropic_engine*>(user_data);
303 *result = nullptr;
304 if (!h || !h->server_manager || !h->orchestrator) { return 1; }
305
306 std::string tier_name = tier ? tier : "";
307 auto all_json = h->server_manager->list_tools();
308 auto all_tools = nlohmann::json::parse(all_json, nullptr, false);
309 auto allowed = resolve_allowed_tools(h, tier_name);
310 auto tool_jsons = filter_tools(all_tools, allowed);
311 auto* adapter = h->orchestrator->get_adapter(tier_name);
312
313 bool ok = all_tools.is_array() && !all_tools.empty()
314 && !tool_jsons.empty() && adapter != nullptr;
315 if (!ok) { return 1; }
316
317 *result = strdup(adapter->format_tools(tool_jsons).c_str());
318 return 0;
319}
320
334 h->engine->set_external_interrupt(
335 [](void* ud) {
336 auto* sm = static_cast<entropic::ServerManager*>(ud);
337 if (sm) { sm->interrupt_external_tools(); }
338 }, h->server_manager.get());
339}
340
354 if (h->stream_observer != nullptr && h->engine) {
355 h->engine->set_stream_observer(
357 }
358}
359
373 if (h->queue_observer != nullptr && h->engine) {
374 h->engine->set_queue_observer(
375 h->queue_observer, h->queue_observer_data);
376 }
377}
378
392 if (h->state_observer != nullptr && h->engine) {
393 h->engine->set_state_observer(
394 h->state_observer, h->state_observer_data);
395 }
396}
397
412 if ((h->critique_start_cb != nullptr || h->critique_end_cb != nullptr)
413 && h->validator) {
414 h->validator->set_critique_callbacks(
418 }
419}
420
437
452 const std::filesystem::path& data_dir,
453 const std::string& shared_prefix) {
454 for (const auto& [name, tier] : h->config.models.tiers) {
456 info.valid = true;
457 auto parsed = entropic::prompts::resolve_tier_identity_full(
458 tier, name, data_dir);
459 info.system_prompt = shared_prefix + parsed.body;
460 info.explicit_completion = !tier.auto_chain.has_value();
461 // E6 (2.0.6-rc18): propagate per-identity caps from frontmatter
462 // so AgentEngine::tri_get_tier_param surfaces them to the loop
463 // and tool executor. -1 = "use global loop_config default".
464 info.max_iterations_override =
465 parsed.frontmatter.max_iterations;
466 info.max_tool_calls_per_turn_override =
467 parsed.frontmatter.max_tool_calls_per_turn;
468 h->engine->set_tier_info(name, info);
469 }
470}
471
472// ── configure_common ───────────────────────────────────────
473
493 const char* parent_id, const char* delegating_tier,
494 const char* target_tier, const char* task, int max_turns,
495 std::string& delegation_id, std::string& child_conversation_id,
496 void* user_data) {
497 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
498 if (sb == nullptr) { return false; }
499 return sb->create_delegation(
500 parent_id ? parent_id : "",
501 delegating_tier ? delegating_tier : "",
502 target_tier ? target_tier : "",
503 task ? task : "",
504 max_turns, delegation_id, child_conversation_id);
505}
506
520 const char* title, std::string& conversation_id,
521 void* user_data) {
522 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
523 if (sb == nullptr) { return false; }
524 conversation_id = sb->create_conversation(
525 title ? title : "session", std::nullopt, std::nullopt);
526 return !conversation_id.empty();
527}
528
535 const char* delegation_id, const char* status,
536 const char* summary, void* user_data) {
537 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
538 if (sb == nullptr || delegation_id == nullptr) { return false; }
539 std::optional<std::string> sum;
540 if (summary != nullptr) { sum = summary; }
541 return sb->complete_delegation(delegation_id,
542 status ? status : "completed", sum);
543}
544
551 const char* conversation_id, const char* messages_json,
552 void* user_data) {
553 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
554 if (sb == nullptr || conversation_id == nullptr
555 || messages_json == nullptr) {
556 return false;
557 }
558 return sb->save_messages(conversation_id, messages_json);
559}
560
567 const char* conversation_id, const char* messages_json,
568 void* user_data) {
569 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
570 if (sb == nullptr || conversation_id == nullptr
571 || messages_json == nullptr) {
572 return false;
573 }
574 return sb->save_snapshot(conversation_id, messages_json);
575}
576
588 const char* delegation_id, std::string& result_json,
589 void* user_data) {
590 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
591 bool ok = sb != nullptr && delegation_id != nullptr;
592 std::string del_json;
593 nlohmann::json del, conv;
594 std::string child_id, target, conv_json;
595 if (ok) {
596 ok = sb->get_delegation_by_id(delegation_id, del_json);
597 }
598 if (ok) {
599 del = nlohmann::json::parse(del_json, nullptr, false);
600 ok = del.is_object();
601 }
602 if (ok) {
603 child_id = del.value("child_conversation_id", std::string{});
604 target = del.value("target_tier", std::string{});
605 ok = !child_id.empty() && !target.empty()
606 && sb->load_conversation(child_id, conv_json);
607 }
608 if (ok) {
609 conv = nlohmann::json::parse(conv_json, nullptr, false);
610 ok = conv.is_object();
611 }
612 if (ok) {
613 conv["target_tier"] = target;
614 conv["delegation_id"] = del.value("id", std::string{});
615 result_json = conv.dump();
616 }
617 return ok;
618}
619
637 si.create_delegation = si_create_delegation;
638 si.complete_delegation = si_complete_delegation;
639 si.save_conversation = si_save_conversation;
640 si.save_snapshot = si_save_snapshot;
641 si.load_delegation_with_messages = si_load_delegation_with_messages;
642 si.user_data = sb;
643 return si;
644}
645
652 if (h->config.log_dir.empty()) { return; }
653 auto db_path = h->config.log_dir / "entropic.db";
654 h->storage = std::make_unique<entropic::SqliteStorageBackend>(db_path);
655 if (h->storage->initialize()) {
656 s_log->info("storage: {}", db_path.string());
657 } else {
658 s_log->warn("storage init failed, continuing without persistence");
659 h->storage.reset();
660 }
661 // gh#32 (v2.1.6): wire StorageInterface into the engine so the
662 // create_delegation/save_conversation paths actually persist (they
663 // were dead code pre-2.1.6 because nothing populated the iface).
664 if (h->storage && h->engine) {
665 h->engine->set_storage(build_storage_iface(h->storage.get()));
666 }
667 h->session_logger = std::make_unique<entropic::SessionLogger>(
668 h->config.log_dir);
669}
670
688static std::vector<std::string> collect_delegatable_tiers(
689 const entropic::ParsedConfig& config) {
690 std::unordered_set<std::string> targets;
691 for (const auto& [source, dests] : config.routing.handoff_rules) {
692 for (const auto& t : dests) { targets.insert(t); }
693 }
694 if (targets.empty()) {
695 for (const auto& [name, tier] : config.models.tiers) {
696 if (name != config.models.default_tier) {
697 targets.insert(name);
698 }
699 }
700 }
701 return {targets.begin(), targets.end()};
702}
703
712 const std::filesystem::path& data_dir) {
713 auto root = h->config.mcp.working_dir.empty()
714 ? std::filesystem::current_path()
715 : std::filesystem::path(h->config.mcp.working_dir);
716 h->server_manager = std::make_unique<entropic::ServerManager>(
717 h->config.permissions, root);
718 auto tier_names = collect_delegatable_tiers(h->config);
719 h->server_manager->init_builtins(
720 h->config.mcp, tier_names, data_dir.string());
721}
722
731static std::string build_shared_prompt_prefix(
733 const std::filesystem::path& data_dir) {
734 std::string constitution, app_ctx;
735 entropic::prompts::load_constitution(
737 data_dir, constitution);
738 entropic::prompts::load_app_context(
740 data_dir, app_ctx);
741 std::string prefix;
742 if (!constitution.empty()) { prefix += constitution + "\n\n"; }
743 if (!app_ctx.empty()) { prefix += app_ctx + "\n\n"; }
744 return prefix;
745}
746
764 const std::string& name,
766 if (fm.allowed_tools.has_value()) {
767 h->tier_allowed_tools[name] = *fm.allowed_tools;
768 }
769 if (!fm.validation_rules.empty()) {
771 }
772 if (fm.relay_single_delegate) {
773 h->engine->set_relay_single_delegate(name);
774 }
775}
776
786 const std::filesystem::path& data_dir) {
787 for (const auto& [name, tier] : h->config.models.tiers) {
788 std::filesystem::path id_path;
789 if (tier.identity.has_value()) {
790 id_path = tier.identity.value();
791 } else if (!tier.identity_disabled) {
792 id_path = data_dir / "prompts" / ("identity_" + name + ".md");
793 }
794 if (id_path.empty() || !std::filesystem::exists(id_path)) {
795 continue;
796 }
798 if (entropic::prompts::load_identity(id_path, id).empty()) {
799 apply_identity_frontmatter(h, name, id.frontmatter);
800 }
801 }
802}
803
821static char* tool_history_json_thunk(size_t count, void* ud) {
822 auto* exec = static_cast<entropic::ToolExecutor*>(ud);
823 if (exec == nullptr) { return nullptr; }
824 auto s = exec->tool_history().to_json(count);
825 if (s.empty() || s == "[]") { return nullptr; }
826 auto* out = static_cast<char*>(std::malloc(s.size() + 1));
827 if (out != nullptr) {
828 std::memcpy(out, s.data(), s.size());
829 out[s.size()] = '\0';
830 }
831 return out;
832}
833
846 h->tool_executor = std::make_unique<entropic::ToolExecutor>(
847 *h->server_manager,
848 h->engine->loop_config(),
849 h->engine->callbacks(),
850 h->engine->build_directive_hooks());
853 const std::vector<entropic::ToolCall>& calls,
854 void* ud) -> std::vector<entropic::Message> {
855 return static_cast<entropic::ToolExecutor*>(ud)
856 ->process_tool_calls(ctx, calls);
857 };
858 tei.user_data = h->tool_executor.get();
860 tei.free_fn = [](char* p) { std::free(p); };
861 h->engine->set_tool_executor(tei);
862}
863
877static char* sp_get_validation(void* ud) {
878 auto* h = static_cast<entropic_engine*>(ud);
879 if (h == nullptr || h->validator == nullptr) { return nullptr; }
880 auto r = h->validator->last_result();
881 nlohmann::json v;
882 v["ran"] = true;
883 switch (r.verdict) {
885 v["verdict"] = "passed"; break;
887 v["verdict"] = "revised"; break;
889 v["verdict"] = "rejected_reverted_length"; break;
891 v["verdict"] = "rejected_max_revisions"; break;
893 v["verdict"] = "skipped"; break;
895 v["verdict"] = "paused_pending_consumer"; break;
897 v["verdict"] = "passed_consumer_override"; break;
898 }
899 v["revisions_applied"] = r.revision_count;
900 // gh#30 (v2.1.5): structured fields the consumer needs to render
901 // a "retry / override / re-prompt" UI without parsing free-form
902 // reason strings.
903 v["attempt_n"] = r.attempt_n;
904 nlohmann::json violations = nlohmann::json::array();
905 for (const auto& vi : r.final_critique.violations) {
906 violations.push_back({
907 {"rule", vi.rule},
908 {"rule_id", vi.rule}, // alias for gh#30 schema
909 {"rule_text", vi.rule}, // alias for gh#30 schema
910 {"excerpt", vi.excerpt},
911 {"quote", vi.excerpt}, // alias matching gh#30 "evidence.quote"
912 {"explanation", vi.explanation},
913 {"severity", "error"}, // gh#30: hard rejection only today
914 });
915 }
916 v["violations"] = violations;
917 return strdup(v.dump().c_str());
918}
919
937 entropic::InferenceInterface& iface,
938 const std::string& constitution_text) {
939 entropic::HookInterface hook_iface;
940 hook_iface.registry = &h->hook_registry;
941 hook_iface.fire_pre = [](void* reg, entropic_hook_point_t pt,
942 const char* json, char** out) -> int {
943 return static_cast<entropic::HookRegistry*>(reg)
944 ->fire_pre(pt, json, out);
945 };
946 hook_iface.fire_post = [](void* reg, entropic_hook_point_t pt,
947 const char* json, char** out) {
948 static_cast<entropic::HookRegistry*>(reg)
949 ->fire_post(pt, json, out);
950 };
951 hook_iface.fire_info = [](void* reg, entropic_hook_point_t pt,
952 const char* json) {
953 static_cast<entropic::HookRegistry*>(reg)->fire_info(pt, json);
954 };
955 h->engine->set_hooks(hook_iface);
956 // E9 (2.0.6-rc19): forward the same hook dispatch to the tool
957 // executor so PRE_TOOL_CALL / POST_TOOL_CALL actually fire.
958 // Prior wiring only touched the engine; tool_executor_ held a
959 // null HookInterface and silently skipped all tool hooks.
960 if (h->tool_executor) {
961 h->tool_executor->set_hooks(hook_iface);
962 }
963
965 && !constitution_text.empty()) {
966 h->validator = std::make_unique<entropic::ConstitutionalValidator>(
967 h->config.constitutional_validation, constitution_text);
968 h->validator->attach(&hook_iface, &iface);
969 // E3 (2.0.6-rc17): expose validator verdict via ON_COMPLETE
970 // hook context.
971 h->engine->set_validation_provider(sp_get_validation, h);
972 s_log->info("Constitutional validator attached (max_revisions={})",
974 }
975}
976
977// ── State provider callbacks ─────────────────────────────
978
984static char* sp_get_config(void* ud) {
985 auto* h = static_cast<entropic_engine*>(ud);
986 nlohmann::json j;
987 j["default_tier"] = h->config.models.default_tier;
988 j["log_level"] = h->config.log_level;
989 j["log_dir"] = h->config.log_dir.string();
990 j["ggml_logging"] = h->config.ggml_logging;
991 return strdup(j.dump().c_str());
992}
993
1008 entropic_engine* h, const std::string& tier_name) {
1009 auto data_dir = entropic::config::resolve_data_dir(h->config);
1010 std::string constitution, app_ctx;
1011 entropic::prompts::load_constitution(
1013 data_dir, constitution);
1014 entropic::prompts::load_app_context(
1016 data_dir, app_ctx);
1017 std::string identity_body;
1018 auto it = h->config.models.tiers.find(tier_name);
1019 if (it != h->config.models.tiers.end()) {
1020 identity_body = entropic::prompts::resolve_tier_identity(
1021 it->second, tier_name, data_dir);
1022 }
1023 std::string out;
1024 if (!constitution.empty()) { out += constitution + "\n\n"; }
1025 if (!app_ctx.empty()) { out += app_ctx + "\n\n"; }
1026 if (!identity_body.empty()) { out += identity_body; }
1027
1028 // Tool defs from the inference adapter (matches inject_tool_prompt).
1029 if (h->inference_iface.get_tool_prompt != nullptr) {
1030 char* tp = nullptr;
1031 int rc = h->inference_iface.get_tool_prompt(
1032 tier_name.c_str(), &tp,
1033 h->inference_iface.tool_prompt_data);
1034 if (rc == 0 && tp != nullptr) {
1035 out += "\n\n";
1036 out += tp;
1037 if (h->inference_iface.free_fn) {
1038 h->inference_iface.free_fn(tp);
1039 }
1040 }
1041 }
1042 return out;
1043}
1044
1060static char* sp_get_identities(void* ud) {
1061 auto* h = static_cast<entropic_engine*>(ud);
1062 nlohmann::json arr = nlohmann::json::array();
1063 for (const auto& [name, _] : h->config.models.tiers) {
1064 nlohmann::json entry;
1065 entry["name"] = name;
1066 entry["assembled_prompt"] =
1068 arr.push_back(std::move(entry));
1069 }
1070 return strdup(arr.dump().c_str());
1071}
1072
1078static char* sp_get_tools(void* ud) {
1079 auto* h = static_cast<entropic_engine*>(ud);
1080 if (!h->server_manager) { return strdup("[]"); }
1081 return strdup(h->server_manager->list_tools().c_str());
1082}
1083
1102static char* sp_get_history(int max_entries, void* ud) {
1103 auto* h = static_cast<entropic_engine*>(ud);
1104 if (!h || !h->engine) { return strdup("[]"); }
1105 const auto& msgs = h->engine->get_messages();
1106 nlohmann::json arr = nlohmann::json::array();
1107 int start = 0;
1108 if (max_entries > 0
1109 && static_cast<int>(msgs.size()) > max_entries) {
1110 start = static_cast<int>(msgs.size()) - max_entries;
1111 }
1112 for (int i = start; i < static_cast<int>(msgs.size()); ++i) {
1113 const auto& m = msgs[static_cast<size_t>(i)];
1114 std::string preview = m.content.size() > 200
1115 ? entropic::facade::utf8_safe_substr(m.content, 200) + "..."
1116 : m.content;
1117 arr.push_back({
1118 {"role", m.role},
1119 {"content_preview", preview},
1120 {"token_count_est", m.content.size() / 4u}
1121 });
1122 }
1123 return strdup(arr.dump().c_str());
1124}
1125
1139static char* sp_get_residency(void* ud) {
1140 auto* h = static_cast<entropic_engine*>(ud);
1141 if (!h || !h->orchestrator) {
1142 return strdup("{\"vram_total_bytes\":0,\"vram_budget_bytes\":0,"
1143 "\"vram_headroom_bytes\":0,\"backend\":\"unknown\","
1144 "\"residency\":[]}");
1145 }
1146 return strdup(h->orchestrator->residency_snapshot_json().c_str());
1147}
1148
1154static char* sp_get_state(void* ud) {
1155 auto* h = static_cast<entropic_engine*>(ud);
1156 nlohmann::json j;
1157 j["engine_state"] = h->configured.load() ? "configured" : "init";
1158 j["default_tier"] = h->config.models.default_tier;
1159
1160 nlohmann::json tiers = nlohmann::json::array();
1161 for (const auto& [name, _] : h->config.models.tiers) {
1162 tiers.push_back(name);
1163 }
1164 j["active_tiers"] = tiers;
1165
1166 if (h->server_manager) {
1167 j["working_dir"] = h->server_manager->project_dir().string();
1168 j["registered_servers"] = h->server_manager->server_names();
1169 }
1170 j["data_dir"] = entropic::config::resolve_data_dir(
1171 h->config).string();
1172 j["log_dir"] = h->config.log_dir.string();
1173 return strdup(j.dump().c_str());
1174}
1175
1188static char* sp_get_metrics(void* ud) {
1189 auto* h = static_cast<entropic_engine*>(ud);
1190 if (!h || !h->engine) { return strdup("{}"); }
1191 auto m = h->engine->last_loop_metrics();
1192 nlohmann::json j;
1193 j["iterations"] = m.iterations;
1194 j["tool_calls"] = m.tool_calls;
1195 j["tokens_used"] = m.tokens_used;
1196 j["errors"] = m.errors;
1197 j["duration_ms"] = m.duration_ms();
1198 // Per-tier breakdown (P2-15 follow-up, 2.0.6-rc16.2)
1199 nlohmann::json per_tier = nlohmann::json::object();
1200 for (auto& [tier, tm] : h->engine->per_tier_metrics()) {
1201 per_tier[tier] = {
1202 {"iterations", tm.iterations},
1203 {"tool_calls", tm.tool_calls},
1204 {"tokens_used", tm.tokens_used},
1205 {"errors", tm.errors},
1206 {"duration_ms", tm.duration_ms()},
1207 };
1208 }
1209 j["per_tier"] = per_tier;
1210 return strdup(j.dump().c_str());
1211}
1212
1218static char* sp_get_docs(const char* section, void* ud) {
1219 (void)section;
1220 (void)ud;
1221 return strdup("");
1222}
1223
1234 const char* query, int max_results, void* ud) {
1235 auto* h = static_cast<entropic_engine*>(ud);
1236 if (h == nullptr || !h->storage || query == nullptr) {
1237 return nullptr;
1238 }
1239 std::string out;
1240 if (!h->storage->search_delegations(query, max_results, out)) {
1241 return nullptr;
1242 }
1243 return strdup(out.c_str());
1244}
1245
1256 const char* delegation_id, void* ud) {
1257 auto* h = static_cast<entropic_engine*>(ud);
1258 if (h == nullptr || !h->storage || delegation_id == nullptr) {
1259 return nullptr;
1260 }
1261 std::string out;
1263 delegation_id, out, h->storage.get())) {
1264 return nullptr;
1265 }
1266 return strdup(out.c_str());
1267}
1268
1281 if (!h->server_manager) { return; }
1282 auto* es = dynamic_cast<entropic::EntropicServer*>(
1283 h->server_manager->get_server("entropic"));
1284 if (es == nullptr) { return; }
1285
1288 sp.get_identities = sp_get_identities;
1289 sp.get_tools = sp_get_tools;
1290 sp.get_history = sp_get_history;
1291 sp.get_state = sp_get_state;
1292 sp.get_metrics = sp_get_metrics;
1293 sp.get_docs = sp_get_docs;
1294 // gh#32 (v2.1.6): storage-backed delegation recall + resume.
1295 sp.search_delegations = sp_search_delegations;
1296 sp.load_delegation_conversation = sp_load_delegation_conversation;
1297 // gh#57 (v2.2.4): VRAM residency-set snapshot.
1298 sp.get_residency = sp_get_residency;
1299 sp.user_data = h;
1300 es->set_state_provider(sp);
1301 s_log->info("State provider wired to entropic server");
1302}
1303
1311 if (!h->validator) { return; }
1312 for (const auto& [name, rules] : h->tier_validation_rules) {
1313 h->validator->set_tier_rules(name, rules);
1314 }
1315}
1316
1326 lc.stream_output = true;
1328 auto it = h->config.models.tiers.find(h->config.models.default_tier);
1329 if (it != h->config.models.tiers.end()) {
1330 lc.context_length = it->second.context_length;
1331 }
1332 return lc;
1333}
1334
1347 if (!h->config.mcp.external.enabled) { return; }
1348 auto project_dir = h->config.config_dir.empty()
1349 ? std::filesystem::current_path()
1350 : h->config.config_dir;
1351 h->external_bridge = std::make_unique<entropic::ExternalBridge>(
1352 h, h->config.mcp.external, project_dir);
1353 if (!h->external_bridge->start()) {
1354 s_log->warn("External MCP bridge failed to start");
1355 h->external_bridge.reset();
1356 }
1357}
1358
1383 if (!h->configured.load()) { return ENTROPIC_OK; }
1384 h->last_error = "handle already configured";
1385 s_log->error("{}", h->last_error);
1387}
1388
1405 entropic_handle_t h, const std::filesystem::path& data_dir) {
1406 h->orchestrator = std::make_unique<entropic::ModelOrchestrator>();
1407 if (!h->orchestrator->initialize(h->config)) {
1408 h->last_error = "orchestrator initialization failed";
1409 s_log->error("{}", h->last_error);
1411 }
1412 // Fallback grammar loading: only if initialize() didn't find
1413 // grammars via config_dir. Avoids overwriting patched grammars.
1414 if (h->orchestrator->grammar_registry().size() == 0) {
1415 h->orchestrator->load_grammars_from(data_dir / "grammars");
1416 }
1417 return ENTROPIC_OK;
1418}
1419
1433 entropic_handle_t h, const std::filesystem::path& data_dir) {
1434 h->mcp_auth = std::make_unique<entropic::MCPAuthorizationManager>();
1435 h->identity_manager = std::make_unique<entropic::IdentityManager>(
1437 // P1-7: route identity changes to prompt-cache invalidation.
1438 h->identity_manager->set_cache_invalidator(
1439 [](void* ud) {
1440 auto* orch = static_cast<entropic::ModelOrchestrator*>(ud);
1441 if (orch) { orch->clear_all_prompt_caches(); }
1442 }, h->orchestrator.get());
1443 init_mcp_servers(h, data_dir);
1444
1445 // gh#58 follow-up (v2.2.6): per-handle InterfaceContext. Pre-v2.2.6
1446 // build_orchestrator_interface stored the context in a process-
1447 // global static, so a second configure freed the first handle's
1448 // context and h1.run() segfaulted on a use-after-free.
1452 h->inference_iface.get_tool_prompt = facade_get_tool_prompt;
1453 h->inference_iface.tool_prompt_data = h;
1454 auto lc = build_loop_config(h);
1455 h->engine = std::make_unique<entropic::AgentEngine>(
1456 h->inference_iface, lc, h->config.compaction);
1457 rewire_observers(h); // gh#40 + fallout (v2.1.10)
1458 wire_external_interrupt(h); // P1-10
1460}
1461
1473 entropic_handle_t h, const std::filesystem::path& data_dir) {
1474 auto shared_prefix = build_shared_prompt_prefix(h, data_dir);
1475 populate_tier_info(h, data_dir, shared_prefix);
1476 cache_tier_allowed_tools(h, data_dir);
1477 h->engine->set_handoff_rules(h->config.routing.handoff_rules);
1478
1479 wire_hooks_and_validator(h, h->inference_iface, shared_prefix);
1481
1484
1485 h->engine->set_system_prompt(
1486 entropic::prompts::assemble(h->config, data_dir));
1487 if (h->session_logger) {
1488 h->engine->set_session_logger(h->session_logger.get());
1489 }
1490}
1491
1498 if (auto rc = reject_if_configured(h); rc != ENTROPIC_OK) { return rc; }
1499 // gh#59 follow-up (v2.3.7): honor console_logging before any init
1500 // logging fires. When false, strip the stderr console sink so the
1501 // file sink (already installed by setup_session) is the only route
1502 // — TUI consumers paint to fd 2 and can't tolerate engine output
1503 // there. Default (true) is a no-op; operators keep stderr logs.
1504 entropic::log::set_console_enabled(h->config.console_logging);
1505 auto data_dir = entropic::config::resolve_data_dir(h->config);
1506 if (auto rc = init_orchestrator(h, data_dir); rc != ENTROPIC_OK) {
1507 return rc;
1508 }
1509
1510 init_engine_and_interfaces(h, data_dir);
1511 wire_prompts_and_persistence(h, data_dir);
1512
1513 h->configured.store(true);
1515 s_log->info("configure complete");
1516 return ENTROPIC_OK;
1517}
1518
1527 entropic_handle_t handle,
1528 const char* config_json) {
1529 if (!handle || !config_json) {
1530 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
1532 }
1533
1534 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
1535
1537
1538 auto err = entropic::config::load_config_from_string(
1539 config_json, handle->bundled_models, handle->config);
1540 if (!err.empty()) {
1541 handle->last_error = err;
1542 s_log->error("configure: {}", err);
1544 }
1545
1546 return configure_common(handle);
1547}
1548
1557 entropic_handle_t handle,
1558 const char* config_path) {
1559 if (!handle || !config_path) {
1560 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
1562 }
1563
1564 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
1565
1567
1568 auto err = entropic::config::load_config_from_file(
1569 config_path, handle->bundled_models, handle->config);
1570 if (!err.empty()) {
1571 handle->last_error = err;
1572 s_log->error("configure_from_file: {}", err);
1574 }
1575
1576 // Parity with configure_dir: if the parsed config specifies a
1577 // log_dir, start session logging there. Without this, consumers
1578 // using the file-based API get no session.log on disk even when
1579 // their YAML declares log_dir.
1580 if (!handle->config.log_dir.empty()) {
1581 entropic::log::setup_session(handle->config.log_dir);
1582 // gh#59 (v2.3.1): also register the per-handle dispatcher
1583 // file sink so log lines emitted within this handle's
1584 // HandleLogScope route to this handle's session.log and
1585 // nowhere else.
1586 entropic::log::register_handle_log(
1587 handle->log_id, handle->config.log_dir);
1588 }
1589
1590 return configure_common(handle);
1591}
1592
1606 entropic_handle_t handle,
1607 const char* project_dir) {
1608 if (!handle) {
1610 }
1611
1612 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
1613
1614 // Session logging FIRST — capture everything from preload through init.
1615 if (project_dir && project_dir[0] != '\0') {
1616 entropic::log::setup_session(project_dir);
1617 // gh#59 (v2.3.1): per-handle dispatcher registration. Routes
1618 // session.log writes for this handle's HandleLogScope-tagged
1619 // threads to this handle's file only — no cross-handle bleed.
1620 entropic::log::register_handle_log(
1621 handle->log_id, project_dir);
1622 }
1623
1625
1626 std::filesystem::path proj_dir = (project_dir && project_dir[0] != '\0')
1627 ? project_dir : "";
1628 auto err = entropic::config::load_layered(
1629 proj_dir, "default_config.yaml",
1630 handle->bundled_models, handle->config);
1631 if (!err.empty()) {
1632 handle->last_error = err;
1633 s_log->error("configure_dir: {}", err);
1635 }
1636
1637 auto rc = configure_common(handle);
1638
1639 // gh#31 (v2.1.6): propagate the configured project_dir into the
1640 // engine so `AgentEngine::get_repo_dir()` uses it as the sandbox
1641 // snapshot source. Pre-2.1.6 this was silently dropped and the
1642 // engine used CWD, snapshotting whatever directory the consumer
1643 // launched from.
1644 if (rc == ENTROPIC_OK && handle->engine && !proj_dir.empty()) {
1645 handle->engine->set_project_dir(std::filesystem::absolute(proj_dir));
1646 }
1647
1648 return rc;
1649}
1650
1662 if (handle == nullptr) {
1663 return;
1664 }
1665 s_log->info("entropic_destroy()");
1666
1667 // Stop external bridge FIRST — it holds a raw pointer to handle
1668 if (handle->external_bridge) {
1669 handle->external_bridge->stop();
1670 handle->external_bridge.reset();
1671 }
1672
1674
1675 // gh#58 follow-up (v2.2.6): release per-handle InterfaceContext
1676 // before the orchestrator unloads, since the context holds a raw
1677 // orchestrator pointer used by the iface callbacks.
1679 handle->inference_iface_ctx = nullptr;
1680
1681 // gh#59 (v2.3.1): release the per-handle session.log file sink so
1682 // a subsequent handle that happens to reuse the same log_id can
1683 // open the file fresh. Safe on never-registered ids.
1684 entropic::log::unregister_handle_log(handle->log_id);
1685
1686 // Phase 1+ subsystem teardown will go here in reverse order.
1687 // Phase 0: struct itself owns hook_registry by value.
1688 delete handle;
1689}
1690
1697const char* entropic_version(void) {
1698 return CONFIG_ENTROPIC_VERSION_STRING;
1699}
1700
1708 return 2;
1709}
1710
1717 if (!handle || !handle->engine) { return 0; }
1718 return handle->engine->seconds_since_last_activity();
1719}
1720
1728void* entropic_alloc(size_t size) {
1729 return malloc(size);
1730}
1731
1738void entropic_free(void* ptr) {
1739 free(ptr);
1740}
1741
1759 entropic_handle_t handle,
1760 const char* input,
1761 char** result_json) {
1762 auto rc = check_orchestrator(handle);
1763 if (rc != ENTROPIC_OK || !input || !result_json || !handle->engine) {
1764 return rc != ENTROPIC_OK ? rc
1765 : (!input || !result_json) ? ENTROPIC_ERROR_INVALID_ARGUMENT
1767 }
1768 try {
1769 auto result = handle->engine->run_turn(input);
1770 *result_json = alloc_cstr(
1771 facade_json::serialize_messages(result));
1772 // Synthetic completion sentinel — lets observers detect the
1773 // end of a non-streaming run. Contract: (token="", len=0).
1774 // (P0-1, 2.0.6-rc16)
1775 if (handle->stream_observer != nullptr) {
1776 handle->stream_observer(
1777 "", 0, handle->stream_observer_data);
1778 }
1779 return ENTROPIC_OK;
1780 } catch (const std::exception& e) {
1781 handle->last_error = e.what();
1782 s_log->error("run: {}", handle->last_error);
1783 // P3-19 follow-up (2.0.6-rc16.2): surface partial context on
1784 // crash so callers can recover tool_results and any partial
1785 // assistant content accumulated before the failure.
1786 try {
1787 *result_json = alloc_cstr(
1788 facade_json::serialize_messages(
1789 handle->engine->get_messages()));
1790 } catch (...) {
1791 *result_json = nullptr;
1792 }
1794 }
1795}
1796
1797/* StreamBridge + think filter moved to inference/stream_think_filter.cpp — Step 4 */
1798
1799/* StreamCtx + stream_chunk_cb → engine->run_streaming() — v2.0.2 */
1800
1814 entropic_handle_t handle,
1815 const char* input,
1816 void (*on_token)(const char* token, size_t len, void* user_data),
1817 void* user_data,
1818 int* cancel_flag) {
1819 auto rc = check_orchestrator(handle);
1820 if (rc != ENTROPIC_OK || !input || !on_token || !handle->engine) {
1821 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
1822 }
1823
1824 // Observer multiplexing is handled inside ResponseGenerator — the
1825 // facade passes on_token through untouched. (P0-1, 2.0.6-rc16)
1826 try {
1827 int code = handle->engine->run_streaming(
1828 input, on_token, user_data, cancel_flag);
1829 if (handle->stream_observer != nullptr) {
1830 handle->stream_observer(
1831 "", 0, handle->stream_observer_data);
1832 }
1833 return code == 1 ? ENTROPIC_ERROR_CANCELLED : ENTROPIC_OK;
1834 } catch (const std::exception& e) {
1835 handle->last_error = e.what();
1836 s_log->error("run_streaming: {}", handle->last_error);
1838 }
1839}
1840
1841// ── gh#37 (v2.1.8): multimodal messages entry points ──────────
1842
1860static std::vector<entropic::Message> parse_and_check_vision(
1861 entropic_handle_t handle,
1862 const char* messages_json,
1863 entropic_error_t& out_rc) {
1864 auto msgs = entropic::parse_messages_json(messages_json);
1866 && handle->orchestrator
1867 && !handle->orchestrator->has_vision_capable_tier()) {
1869 return {};
1870 }
1871 out_rc = ENTROPIC_OK;
1872 return msgs;
1873}
1874
1893 entropic_handle_t handle,
1894 const char* messages_json,
1895 char** result_json) {
1897 auto msgs = parse_and_check_vision(handle, messages_json, vrc);
1898 if (vrc != ENTROPIC_OK) { return vrc; }
1899 auto result = handle->engine->run_turn(std::move(msgs));
1900 *result_json = alloc_cstr(
1901 facade_json::serialize_messages(result));
1902 if (handle->stream_observer != nullptr) {
1903 handle->stream_observer(
1904 "", 0, handle->stream_observer_data);
1905 }
1906 return ENTROPIC_OK;
1907}
1908
1925 entropic_handle_t handle,
1926 const char* messages_json,
1927 char** result_json) {
1928 auto rc = check_orchestrator(handle);
1929 if (rc != ENTROPIC_OK
1930 || !messages_json || !result_json || !handle->engine) {
1931 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
1932 }
1933 try {
1934 return run_messages_inner(handle, messages_json, result_json);
1935 } catch (const std::exception& e) {
1936 handle->last_error = e.what();
1937 s_log->error("run_messages: {}", handle->last_error);
1939 }
1940}
1941
1953 entropic_handle_t handle,
1954 const char* messages_json,
1955 void (*on_token)(const char* token, size_t len, void* user_data),
1956 void* user_data,
1957 int* cancel_flag) {
1959 auto msgs = parse_and_check_vision(handle, messages_json, vrc);
1960 if (vrc != ENTROPIC_OK) { return vrc; }
1961 int code = handle->engine->run_streaming(
1962 std::move(msgs), on_token, user_data, cancel_flag);
1963 if (handle->stream_observer != nullptr) {
1964 handle->stream_observer(
1965 "", 0, handle->stream_observer_data);
1966 }
1967 return code == 1 ? ENTROPIC_ERROR_CANCELLED : ENTROPIC_OK;
1968}
1969
1988 entropic_handle_t handle,
1989 const char* messages_json,
1990 void (*on_token)(const char* token, size_t len, void* user_data),
1991 void* user_data,
1992 int* cancel_flag) {
1993 auto rc = check_orchestrator(handle);
1994 if (rc != ENTROPIC_OK
1995 || !messages_json || !on_token || !handle->engine) {
1996 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
1997 }
1998 try {
2000 handle, messages_json, on_token, user_data, cancel_flag);
2001 } catch (const std::exception& e) {
2002 handle->last_error = e.what();
2003 s_log->error("run_messages_streaming: {}", handle->last_error);
2005 }
2006}
2007
2018 entropic_handle_t handle,
2019 void (*observer)(const char* token, size_t len, void* user_data),
2020 void* user_data) {
2021 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2022 handle->stream_observer = observer;
2023 handle->stream_observer_data = user_data;
2024 // Propagate to engine so every generation path (streaming, batch,
2025 // and child-loop delegations) reaches the observer. (P0-1, 2.0.6-rc16)
2026 if (handle->engine) {
2027 handle->engine->set_stream_observer(observer, user_data);
2028 }
2029 return ENTROPIC_OK;
2030}
2031
2032// ── gh#30 (v2.1.5): validation retry controls ─────────────
2033
2040 entropic_handle_t handle, int enabled) {
2041 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2042 if (handle->validator) {
2043 handle->validator->set_auto_retry(enabled != 0);
2044 }
2045 return ENTROPIC_OK;
2046}
2047
2054 entropic_handle_t handle) {
2055 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2056 if (!handle->validator) { return ENTROPIC_ERROR_INVALID_STATE; }
2057 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2058 return handle->validator->resume_retry();
2059}
2060
2067 entropic_handle_t handle) {
2068 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2069 if (!handle->validator) { return ENTROPIC_ERROR_INVALID_STATE; }
2070 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2071 return handle->validator->accept_last();
2072}
2073
2080 entropic_handle_t handle,
2082 void* user_data) {
2083 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2084 if (handle->validator) {
2085 handle->validator->set_attempt_boundary_cb(cb, user_data);
2086 }
2087 return ENTROPIC_OK;
2088}
2089
2101 entropic_handle_t handle,
2102 ent_delegation_start_cb on_start,
2103 ent_delegation_complete_cb on_complete,
2104 void* user_data) {
2105 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2106 if (handle->engine) {
2107 handle->engine->set_delegation_callbacks(
2108 on_start, on_complete, user_data);
2109 }
2110 return ENTROPIC_OK;
2111}
2112
2130 entropic_handle_t handle,
2131 void (*observer)(int state, void* user_data),
2132 void* user_data) {
2133 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2134 handle->state_observer = observer;
2135 handle->state_observer_data = user_data;
2136 if (handle->engine) {
2137 // gh#40 fallout (v2.1.10): route through the engine's
2138 // persistent state-observer slot rather than the legacy
2139 // EngineCallbacks::on_state_change. The legacy path is
2140 // wiped by run_streaming's set_callbacks() shuffle, so
2141 // wiring there silently failed for streaming runs (the
2142 // exact bridge use case this API was designed for).
2143 handle->engine->set_state_observer(observer, user_data);
2144 }
2145 return ENTROPIC_OK;
2146}
2147
2167 entropic_handle_t handle,
2168 void (*start_cb)(void* user_data),
2169 void (*end_cb)(void* user_data),
2170 void* user_data) {
2171 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2172 handle->critique_start_cb = start_cb;
2173 handle->critique_end_cb = end_cb;
2174 handle->critique_cb_data = user_data;
2175 if (handle->validator) {
2176 handle->validator->set_critique_callbacks(
2177 start_cb, end_cb, user_data);
2178 }
2179 return ENTROPIC_OK;
2180}
2181
2191 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2192 if (!handle->engine) { return ENTROPIC_ERROR_INVALID_STATE; }
2193 handle->engine->interrupt();
2194 return ENTROPIC_OK;
2195}
2196
2197// ── Mid-generation user-message queue (gh#40, v2.1.10) ────────
2198
2212 entropic_handle_t handle, const char* message) {
2214 if (!handle) {
2216 } else if (!message) {
2218 } else if (!handle->engine || !handle->engine->is_running()) {
2220 } else if (!handle->engine->queue_user_message(message)) {
2222 }
2223 return rc;
2224}
2225
2232 entropic_handle_t handle, size_t* count) {
2233 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2234 if (!count) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2235 *count = handle->engine
2236 ? handle->engine->user_message_queue_depth() : 0;
2237 return ENTROPIC_OK;
2238}
2239
2246 entropic_handle_t handle) {
2247 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2248 if (handle->engine) {
2249 handle->engine->clear_user_message_queue();
2250 }
2251 return ENTROPIC_OK;
2252}
2253
2266 entropic_handle_t handle,
2267 void (*observer)(const char*, size_t, void*),
2268 void* user_data) {
2269 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2270 handle->queue_observer = observer;
2271 handle->queue_observer_data = user_data;
2272 if (handle->engine) {
2273 handle->engine->set_queue_observer(observer, user_data);
2274 }
2275 return ENTROPIC_OK;
2276}
2277
2278// ── Conversation Context (v2.0.1) ────────────────────────────
2279
2288 if (!handle || !handle->engine) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2289 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2290 handle->engine->clear_conversation();
2291 return ENTROPIC_OK;
2292}
2293
2303 entropic_handle_t handle, char** messages_json) {
2304 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2305 if (!messages_json) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2306 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2307 *messages_json = alloc_cstr(
2308 facade_json::serialize_messages(handle->engine->get_messages()));
2309 return ENTROPIC_OK;
2310}
2311
2321 entropic_handle_t handle, size_t* count) {
2322 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2323 if (!count) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2324 *count = handle->engine->message_count();
2325 return ENTROPIC_OK;
2326}
2327
2343 entropic_handle_t handle,
2344 size_t* tokens_used,
2345 size_t* capacity) {
2346 if (!handle || !handle->engine) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2347 if (!tokens_used || !capacity) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2348 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2349 auto [used, max] = handle->engine->context_usage(
2350 handle->engine->get_messages());
2351 *tokens_used = static_cast<size_t>(used);
2352 *capacity = static_cast<size_t>(max);
2353 return max > 0 ? ENTROPIC_OK : ENTROPIC_ERROR_INVALID_STATE;
2354}
2355
2370 entropic_handle_t handle, char** out) {
2371 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2372 if (!out) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2373 *out = sp_get_metrics(handle);
2374 return ENTROPIC_OK;
2375}
2376
2377// ── LoRA Adapter APIs (v1.9.2 → v2.0.0) ────────────────────
2378
2392 entropic_handle_t handle,
2393 const char* adapter_name,
2394 const char* adapter_path,
2395 const char* base_model_path,
2396 float scale)
2397{
2398 auto rc = check_orchestrator(handle);
2399 if (rc != ENTROPIC_OK || !adapter_name || !adapter_path || !base_model_path) {
2400 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2401 }
2402 try {
2403 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2404 auto tier = handle->config.models.find_tier_by_path(base_model_path);
2405 if (tier.empty()) {
2406 throw std::runtime_error("no tier for model: "
2407 + std::string(base_model_path));
2408 }
2409 auto* base = handle->orchestrator->get_backend(tier);
2410 auto* llama = dynamic_cast<entropic::LlamaCppBackend*>(base);
2411 if (!llama || !llama->llama_model_ptr()) {
2412 throw std::runtime_error("backend not ready for tier: " + tier);
2413 }
2414 bool ok = handle->orchestrator->adapter_manager().load(
2415 adapter_name, adapter_path, llama->llama_model_ptr(), scale);
2417 } catch (const std::exception& e) {
2418 handle->last_error = e.what();
2420 }
2421}
2422
2435 entropic_handle_t handle,
2436 const char* adapter_name)
2437{
2438 auto rc = check_orchestrator(handle);
2439 if (rc != ENTROPIC_OK || !adapter_name) {
2440 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2441 }
2442 try {
2443 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2444 auto& mgr = handle->orchestrator->adapter_manager();
2445 auto info = mgr.info(adapter_name);
2446 if (info.state == entropic::AdapterState::COLD) {
2447 throw std::runtime_error("adapter not loaded: "
2448 + std::string(adapter_name));
2449 }
2450 auto tier = handle->orchestrator->last_used_tier();
2451 auto* base = handle->orchestrator->get_backend(tier);
2452 auto* llama = dynamic_cast<entropic::LlamaCppBackend*>(base);
2453 mgr.unload(adapter_name, llama ? llama->llama_context_ptr() : nullptr);
2454 return ENTROPIC_OK;
2455 } catch (const std::exception& e) {
2456 handle->last_error = e.what();
2458 }
2459}
2460
2473 entropic_handle_t handle,
2474 const char* adapter_name)
2475{
2476 auto rc = check_orchestrator(handle);
2477 if (rc != ENTROPIC_OK || !adapter_name) {
2478 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2479 }
2480 try {
2481 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2482 auto tier = handle->orchestrator->last_used_tier();
2483 auto* base = handle->orchestrator->get_backend(tier);
2484 auto* llama = dynamic_cast<entropic::LlamaCppBackend*>(base);
2485 if (!llama || !llama->llama_context_ptr()) {
2486 throw std::runtime_error("no active llama context for swap");
2487 }
2488 bool ok = handle->orchestrator->adapter_manager().swap(
2489 adapter_name, llama->llama_context_ptr());
2491 } catch (const std::exception& e) {
2492 handle->last_error = e.what();
2494 }
2495}
2496
2508 entropic_handle_t handle,
2509 const char* adapter_name)
2510{
2511 if (!handle || !handle->configured.load()
2512 || !handle->orchestrator || !adapter_name) {
2513 return -1;
2514 }
2515 try {
2516 auto st = handle->orchestrator->adapter_manager().state(adapter_name);
2517 return static_cast<int>(st);
2518 } catch (const std::exception& e) {
2519 handle->last_error = e.what();
2520 s_log->error("adapter_state: {}", handle->last_error);
2521 return -1;
2522 }
2523}
2524
2536 entropic_handle_t handle,
2537 const char* adapter_name)
2538{
2539 if (!handle || !handle->configured.load()
2540 || !handle->orchestrator || !adapter_name) {
2541 return nullptr;
2542 }
2543 try {
2544 auto ai = handle->orchestrator->adapter_manager().info(adapter_name);
2545 return alloc_cstr(
2546 facade_json::serialize_adapter_info(ai).c_str());
2547 } catch (const std::exception& e) {
2548 handle->last_error = e.what();
2549 s_log->error("adapter_info: {}", handle->last_error);
2550 return nullptr;
2551 }
2552}
2553
2565{
2566 if (!handle || !handle->configured.load() || !handle->orchestrator) {
2567 return nullptr;
2568 }
2569 try {
2570 auto adapters = handle->orchestrator->adapter_manager().list_adapters();
2571 return alloc_cstr(
2572 facade_json::serialize_adapter_list(adapters).c_str());
2573 } catch (const std::exception& e) {
2574 handle->last_error = e.what();
2575 s_log->error("adapter_list: {}", handle->last_error);
2576 return nullptr;
2577 }
2578}
2579
2580// ── Grammar Registry APIs (v1.9.3 → v2.0.0) ────────────────
2581
2595 entropic_handle_t handle,
2596 const char* key,
2597 const char* gbnf_content)
2598{
2599 auto rc = check_orchestrator(handle);
2600 if (rc != ENTROPIC_OK || !key || !gbnf_content) {
2601 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2602 }
2603 try {
2604 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2605 bool ok = handle->orchestrator->grammar_registry()
2606 .register_grammar(key, gbnf_content);
2607 s_log->info("grammar_register: key={} ok={}", key, ok);
2609 } catch (const std::exception& e) {
2610 handle->last_error = e.what();
2611 s_log->error("grammar_register: {}", handle->last_error);
2613 }
2614}
2615
2628 entropic_handle_t handle,
2629 const char* key,
2630 const char* path)
2631{
2632 auto rc = check_orchestrator(handle);
2633 if (rc != ENTROPIC_OK || !key || !path) {
2634 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2635 }
2636 try {
2637 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2638 bool ok = handle->orchestrator->grammar_registry()
2639 .register_from_file(key, path);
2640 s_log->info("grammar_register_file: key={} ok={}", key, ok);
2641 return ok ? ENTROPIC_OK : ENTROPIC_ERROR_IO;
2642 } catch (const std::exception& e) {
2643 handle->last_error = e.what();
2644 s_log->error("grammar_register_file: {}", handle->last_error);
2646 }
2647}
2648
2661 entropic_handle_t handle,
2662 const char* key)
2663{
2664 auto rc = check_orchestrator(handle);
2665 if (rc != ENTROPIC_OK || !key) {
2666 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2667 }
2668 try {
2669 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2670 bool ok = handle->orchestrator->grammar_registry().deregister(key);
2671 s_log->info("grammar_deregister: key={} ok={}", key, ok);
2673 } catch (const std::exception& e) {
2674 handle->last_error = e.what();
2675 s_log->error("grammar_deregister: {}", handle->last_error);
2677 }
2678}
2679
2691 entropic_handle_t handle,
2692 const char* key)
2693{
2694 if (!handle || !handle->configured.load()
2695 || !handle->orchestrator || !key) {
2696 return nullptr;
2697 }
2698 try {
2699 auto content = handle->orchestrator->grammar_registry().get(key);
2700 return content.empty() ? nullptr : alloc_cstr(content.c_str());
2701 } catch (const std::exception& e) {
2702 handle->last_error = e.what();
2703 s_log->error("grammar_get: {}", handle->last_error);
2704 return nullptr;
2705 }
2706}
2707
2719char* entropic_grammar_validate(const char* gbnf_content) {
2720 if (!gbnf_content) { return alloc_cstr("null input"); }
2721 try {
2722 auto err = entropic::GrammarRegistry::validate(gbnf_content);
2723 return err.empty() ? nullptr : alloc_cstr(err.c_str());
2724 } catch (const std::exception& e) {
2725 return alloc_cstr(e.what());
2726 }
2727}
2728
2741{
2742 if (!handle || !handle->configured.load() || !handle->orchestrator) {
2743 return nullptr;
2744 }
2745 try {
2746 auto entries = handle->orchestrator->grammar_registry().list();
2747 nlohmann::json arr = nlohmann::json::array();
2748 for (const auto& e : entries) {
2749 arr.push_back({{"key", e.key},
2750 {"source", e.source},
2751 {"validated", e.validated},
2752 {"error", e.error}});
2753 }
2754 return alloc_cstr(arr.dump().c_str());
2755 } catch (const std::exception& e) {
2756 handle->last_error = e.what();
2757 s_log->error("grammar_list: {}", handle->last_error);
2758 return nullptr;
2759 }
2760}
2761
2762// ── GPU Resource Profile APIs (v1.9.7 → v2.0.0) ─────────────
2763
2777 entropic_handle_t handle,
2778 const char* profile_json)
2779{
2780 auto rc = check_orchestrator(handle);
2781 if (rc != ENTROPIC_OK || !profile_json) {
2782 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2783 }
2784 try {
2785 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2786 auto j = nlohmann::json::parse(profile_json);
2788 p.name = j.value("name", "");
2789 if (p.name.empty()) { throw std::invalid_argument("missing 'name'"); }
2790 p.n_batch = j.value("n_batch", 512);
2791 p.n_threads = j.value("n_threads", 0);
2792 p.n_threads_batch = j.value("n_threads_batch", 0);
2793 p.description = j.value("description", "");
2794 bool ok = handle->orchestrator->profile_registry()
2795 .register_profile(p);
2796 s_log->info("profile_register: name={} ok={}", p.name, ok);
2798 } catch (const std::exception& e) {
2799 handle->last_error = e.what();
2800 s_log->error("profile_register: {}", handle->last_error);
2802 }
2803}
2804
2817 entropic_handle_t handle,
2818 const char* name)
2819{
2820 auto rc = check_orchestrator(handle);
2821 if (rc != ENTROPIC_OK || !name) {
2822 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2823 }
2824 try {
2825 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2826 bool ok = handle->orchestrator->profile_registry().deregister(name);
2827 s_log->info("profile_deregister: name={} ok={}", name, ok);
2829 } catch (const std::exception& e) {
2830 handle->last_error = e.what();
2831 s_log->error("profile_deregister: {}", handle->last_error);
2833 }
2834}
2835
2849 entropic_handle_t handle,
2850 const char* name)
2851{
2852 if (!handle || !handle->configured.load()
2853 || !handle->orchestrator || !name) {
2854 return nullptr;
2855 }
2856 try {
2857 auto p = handle->orchestrator->profile_registry().get(name);
2858 nlohmann::json j;
2859 j["name"] = p.name;
2860 j["n_batch"] = p.n_batch;
2861 j["n_threads"] = p.n_threads;
2862 j["n_threads_batch"] = p.n_threads_batch;
2863 j["description"] = p.description;
2864 return alloc_cstr(j.dump().c_str());
2865 } catch (const std::exception& e) {
2866 handle->last_error = e.what();
2867 s_log->error("profile_get: {}", handle->last_error);
2868 return nullptr;
2869 }
2870}
2871
2883{
2884 if (!handle || !handle->configured.load() || !handle->orchestrator) {
2885 return nullptr;
2886 }
2887 try {
2888 auto names = handle->orchestrator->profile_registry().list();
2889 nlohmann::json arr = nlohmann::json(names);
2890 return alloc_cstr(arr.dump().c_str());
2891 } catch (const std::exception& e) {
2892 handle->last_error = e.what();
2893 s_log->error("profile_list: {}", handle->last_error);
2894 return nullptr;
2895 }
2896}
2897
2898// ── Throughput Query APIs (v1.9.7 → v2.0.0) ─────────────────
2899
2913 entropic_handle_t handle,
2914 const char* model_path)
2915{
2916 (void)model_path;
2917 if (!handle || !handle->configured.load() || !handle->orchestrator) {
2918 return 0.0;
2919 }
2920 try {
2921 return handle->orchestrator->throughput_tracker().tok_per_sec();
2922 } catch (const std::exception& e) {
2923 handle->last_error = e.what();
2924 s_log->error("throughput_tok_per_sec: {}", handle->last_error);
2925 return 0.0;
2926 }
2927}
2928
2941 entropic_handle_t handle,
2942 const char* model_path)
2943{
2944 (void)model_path;
2945 if (!handle || !handle->configured.load() || !handle->orchestrator) {
2946 return;
2947 }
2948 try {
2949 handle->orchestrator->throughput_tracker().reset();
2950 s_log->info("throughput_reset: data cleared");
2951 } catch (const std::exception& e) {
2952 handle->last_error = e.what();
2953 s_log->error("throughput_reset: {}", handle->last_error);
2954 }
2955}
2956
2957// ── MCP Authorization APIs (v1.9.4 → v2.0.0) ────────────────
2958
2967 entropic_handle_t handle,
2968 const char* identity_name,
2969 const char* pattern,
2971{
2972 auto rc = check_mcp_auth(handle);
2973 if (rc != ENTROPIC_OK || !identity_name || !pattern) {
2974 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2975 }
2976 auto lvl = static_cast<entropic::MCPAccessLevel>(level);
2977 return handle->mcp_auth->grant(identity_name, pattern, lvl);
2978}
2979
2988 entropic_handle_t handle,
2989 const char* identity_name,
2990 const char* pattern)
2991{
2992 auto rc = check_mcp_auth(handle);
2993 if (rc != ENTROPIC_OK || !identity_name || !pattern) {
2994 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2995 }
2996 return handle->mcp_auth->revoke(identity_name, pattern);
2997}
2998
3007 entropic_handle_t handle,
3008 const char* identity_name,
3009 const char* tool_name,
3011{
3012 if (!handle || !handle->configured.load()
3013 || !handle->mcp_auth || !identity_name || !tool_name) {
3014 return -1;
3015 }
3016 auto lvl = static_cast<entropic::MCPAccessLevel>(level);
3017 return handle->mcp_auth->check_access(identity_name, tool_name, lvl) ? 1 : 0;
3018}
3019
3028 entropic_handle_t handle,
3029 const char* identity_name)
3030{
3031 if (!handle || !handle->configured.load()
3032 || !handle->mcp_auth || !identity_name) {
3033 return nullptr;
3034 }
3035 try {
3036 auto keys = handle->mcp_auth->list_keys(identity_name);
3037 nlohmann::json arr = nlohmann::json::array();
3038 for (const auto& k : keys) {
3039 arr.push_back({{"pattern", k.tool_pattern},
3040 {"level", static_cast<int>(k.level)}});
3041 }
3042 return alloc_cstr(arr.dump().c_str());
3043 } catch (const std::exception& e) {
3044 handle->last_error = e.what();
3045 return nullptr;
3046 }
3047}
3048
3057 entropic_handle_t handle,
3058 const char* granter,
3059 const char* grantee,
3060 const char* pattern,
3062{
3063 auto rc = check_mcp_auth(handle);
3064 if (rc != ENTROPIC_OK || !granter || !grantee || !pattern) {
3065 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3066 }
3067 auto lvl = static_cast<entropic::MCPAccessLevel>(level);
3068 return handle->mcp_auth->grant_from(granter, grantee, pattern, lvl);
3069}
3070
3079{
3080 if (!handle || !handle->configured.load() || !handle->mcp_auth) {
3081 return nullptr;
3082 }
3083 try {
3084 auto json = handle->mcp_auth->serialize_all();
3085 return alloc_cstr(json.c_str());
3086 } catch (const std::exception& e) {
3087 handle->last_error = e.what();
3088 return nullptr;
3089 }
3090}
3091
3100 entropic_handle_t handle,
3101 const char* json)
3102{
3103 auto rc = check_mcp_auth(handle);
3104 if (rc != ENTROPIC_OK || !json) {
3105 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3106 }
3107 bool ok = handle->mcp_auth->deserialize_all(json);
3109}
3110
3111// ── Dynamic Identity Management APIs (v1.9.6 → v2.0.0) ──────
3112
3121 entropic_handle_t handle,
3122 const char* config_json)
3123{
3124 auto rc = check_identity(handle);
3125 if (rc != ENTROPIC_OK || !config_json) {
3126 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3127 }
3128 try {
3129 auto j = nlohmann::json::parse(config_json);
3131 cfg.name = j.value("name", "");
3132 cfg.system_prompt = j.value("system_prompt", "");
3133 if (j.contains("focus") && j["focus"].is_array()) {
3134 cfg.focus = j["focus"].get<std::vector<std::string>>();
3135 }
3137 return handle->identity_manager->create(cfg);
3138 } catch (const std::exception& e) {
3139 handle->last_error = e.what();
3141 }
3142}
3143
3152 entropic_handle_t handle,
3153 const char* name,
3154 const char* config_json)
3155{
3156 auto rc = check_identity(handle);
3157 if (rc != ENTROPIC_OK || !name || !config_json) {
3158 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3159 }
3160 try {
3161 auto j = nlohmann::json::parse(config_json);
3163 cfg.name = name;
3164 cfg.system_prompt = j.value("system_prompt", "");
3165 if (j.contains("focus") && j["focus"].is_array()) {
3166 cfg.focus = j["focus"].get<std::vector<std::string>>();
3167 }
3169 return handle->identity_manager->update(name, cfg);
3170 } catch (const std::exception& e) {
3171 handle->last_error = e.what();
3173 }
3174}
3175
3184 entropic_handle_t handle,
3185 const char* name)
3186{
3187 auto rc = check_identity(handle);
3188 if (rc != ENTROPIC_OK || !name) {
3189 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3190 }
3191 return handle->identity_manager->destroy(name);
3192}
3193
3202 entropic_handle_t handle,
3203 const char* name)
3204{
3205 if (!handle || !handle->identity_manager || !name) { return nullptr; }
3206 try {
3207 auto* cfg = handle->identity_manager->get(name);
3208 if (!cfg) { throw std::runtime_error("identity not found"); }
3209 nlohmann::json j;
3210 j["name"] = cfg->name;
3211 j["system_prompt"] = cfg->system_prompt;
3212 j["origin"] = (cfg->origin == entropic::IdentityOrigin::STATIC)
3213 ? "static" : "dynamic";
3214 return alloc_cstr(j.dump().c_str());
3215 } catch (...) {
3216 return nullptr;
3217 }
3218}
3219
3228{
3229 if (!handle || !handle->identity_manager) { return nullptr; }
3230 try {
3231 auto names = handle->identity_manager->list();
3232 nlohmann::json arr(names);
3233 return alloc_cstr(arr.dump().c_str());
3234 } catch (...) {
3235 return nullptr;
3236 }
3237}
3238
3247 entropic_handle_t handle,
3248 size_t* total,
3249 size_t* dynamic)
3250{
3251 auto rc = check_identity(handle);
3252 if (rc != ENTROPIC_OK || !total) {
3253 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3254 }
3255 *total = handle->identity_manager->count();
3256 if (dynamic) { *dynamic = handle->identity_manager->count_dynamic(); }
3257 return ENTROPIC_OK;
3258}
3259
3260// ── Log-Probability Evaluation APIs (v1.9.10 → v2.0.0) ──────
3261
3274 entropic_handle_t handle,
3275 const char* model_id,
3276 const int32_t* tokens,
3277 int n_tokens,
3279{
3280 auto rc = check_orchestrator(handle);
3281 if (rc != ENTROPIC_OK || !model_id || !tokens || !result || n_tokens < 2) {
3282 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3283 }
3284 try {
3285 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3286 auto* backend = require_active_backend(handle, model_id);
3287 auto lr = backend->evaluate_logprobs(tokens, n_tokens);
3288 result->n_tokens = lr.n_tokens;
3289 result->n_logprobs = lr.n_logprobs;
3290 result->perplexity = lr.perplexity;
3291 result->total_logprob = lr.total_logprob;
3292 result->logprobs = static_cast<float*>(
3293 malloc(sizeof(float) * lr.logprobs.size()));
3294 std::copy(lr.logprobs.begin(), lr.logprobs.end(),
3295 result->logprobs);
3296 result->tokens = static_cast<int32_t*>(
3297 malloc(sizeof(int32_t) * lr.tokens.size()));
3298 std::copy(lr.tokens.begin(), lr.tokens.end(),
3299 result->tokens);
3300 return ENTROPIC_OK;
3301 } catch (const std::exception& e) {
3302 handle->last_error = e.what();
3303 s_log->error("get_logprobs: {}", handle->last_error);
3305 }
3306}
3307
3319 entropic_handle_t handle,
3320 const char* model_id,
3321 const int32_t* tokens,
3322 int n_tokens,
3323 float* perplexity)
3324{
3325 auto rc = check_orchestrator(handle);
3326 if (rc != ENTROPIC_OK || !model_id || !tokens || !perplexity || n_tokens < 2) {
3327 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3328 }
3329 try {
3330 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3331 auto* backend = require_active_backend(handle, model_id);
3332 *perplexity = backend->compute_perplexity(tokens, n_tokens);
3333 return ENTROPIC_OK;
3334 } catch (const std::exception& e) {
3335 handle->last_error = e.what();
3336 s_log->error("compute_perplexity: {}", handle->last_error);
3338 }
3339}
3340
3351{
3352 if (result == nullptr) {
3353 return;
3354 }
3355 free(result->logprobs);
3356 result->logprobs = nullptr;
3357 free(result->tokens);
3358 result->tokens = nullptr;
3359}
3360
3361// ── Vision Query API (v1.9.11 → v2.0.0) ─────────────────────
3362
3375 entropic_handle_t handle,
3376 const char* model_id)
3377{
3378 if (!handle || !handle->configured.load()
3379 || !handle->orchestrator || !model_id) {
3380 return 0;
3381 }
3382 try {
3383 auto* backend = handle->orchestrator->get_backend(model_id);
3384 return (backend && backend->supports(
3386 } catch (const std::exception& e) {
3387 handle->last_error = e.what();
3388 s_log->error("model_has_vision: {}", handle->last_error);
3389 return 0;
3390 }
3391}
3392
3393// ── Constitutional Validation APIs (v1.9.8 → v2.0.0) ────────
3394
3403 entropic_handle_t handle,
3404 bool enabled)
3405{
3406 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
3407 if (!handle->validator) { return ENTROPIC_ERROR_INVALID_STATE; }
3408 handle->validator->set_global_enabled(enabled);
3409 return ENTROPIC_OK;
3410}
3411
3420 entropic_handle_t handle,
3421 const char* identity_name,
3422 bool enabled)
3423{
3424 if (!handle || !handle->validator) {
3425 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
3427 }
3428 if (!identity_name) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
3429 handle->validator->set_identity_validation(identity_name, enabled);
3430 return ENTROPIC_OK;
3431}
3432
3441{
3442 if (!handle || !handle->validator) { return nullptr; }
3443 try {
3444 auto result = handle->validator->last_result();
3445 nlohmann::json j;
3446 j["content"] = result.content;
3447 j["was_revised"] = result.was_revised;
3448 j["revision_count"] = result.revision_count;
3449 return alloc_cstr(j.dump().c_str());
3450 } catch (...) {
3451 return nullptr;
3452 }
3453}
3454
3465 entropic_handle_t handle,
3466 char** prompt_out) {
3467 if (handle == nullptr || prompt_out == nullptr) {
3469 }
3470 (void)handle;
3471 static const char* prompt =
3472 "[SYSTEM DIRECTIVE: SELF-DIAGNOSIS]\n\n"
3473 "Analyze your recent actions and identify any issues. "
3474 "Follow these steps:\n\n"
3475 "1. Call entropic.diagnose to get a full engine state "
3476 "snapshot.\n"
3477 "2. Review the tool call history for:\n"
3478 " - Repeated failures (same tool, same error)\n"
3479 " - Duplicate tool calls (circuit breaker risk)\n"
3480 " - Tool calls that returned errors\n"
3481 " - Unexpected state (wrong phase, wrong tier)\n"
3482 "3. Review your reasoning for:\n"
3483 " - Actions that didn't achieve the stated goal\n"
3484 " - Unnecessary tool calls\n"
3485 " - Missing context that led to errors\n"
3486 "4. Produce a structured assessment:\n"
3487 " - FINDINGS: What went wrong (be specific)\n"
3488 " - ROOT CAUSE: Why it went wrong\n"
3489 " - RECOMMENDATION: What to do differently\n\n"
3490 "Be honest and specific. The goal is accurate "
3491 "self-assessment, not self-defense.\n";
3492 *prompt_out = alloc_cstr(prompt);
3493 return ENTROPIC_OK;
3494}
3495
3517 entropic_handle_t handle,
3518 int* compatible,
3519 char** diagnostic) {
3520 if (handle == nullptr || compatible == nullptr) {
3522 }
3523 if (!handle->orchestrator) {
3525 }
3526 auto info = handle->orchestrator->check_speculative_compat();
3527 *compatible = info.compatible ? 1 : 0;
3528 if (diagnostic != nullptr) {
3529 *diagnostic = info.compatible
3530 ? nullptr
3531 : alloc_cstr(info.diagnostic);
3532 }
3533 return ENTROPIC_OK;
3534}
3535
3536/* ── VRAM-aware tier residency (v2.2.4, gh#57) ─────────── */
3537
3551 entropic_handle_t handle,
3553 void* user_data) {
3554 if (handle == nullptr) { return ENTROPIC_ERROR_INVALID_HANDLE; }
3555 // Engine-not-configured (no orchestrator yet) and observer==nullptr
3556 // both collapse to a no-op set: pre-configure registration is
3557 // ignored (consumers must re-register after configure_*), and
3558 // an explicit nullptr clears any prior slot. Done as one branch
3559 // to stay under the knots return-count gate.
3561 if (observer != nullptr) {
3562 fn = [observer, user_data](
3564 const std::string& tier_name,
3565 const std::string& model_path,
3566 size_t footprint) {
3567 observer(static_cast<entropic_residency_event_t>(event),
3568 tier_name.c_str(),
3569 model_path.c_str(),
3570 footprint,
3571 user_data);
3572 };
3573 }
3574 if (handle->orchestrator) {
3575 handle->orchestrator->set_residency_observer(std::move(fn));
3576 }
3577 return ENTROPIC_OK;
3578}
3579
3593 entropic_handle_t handle,
3594 char** out_json) {
3596 if (handle == nullptr || out_json == nullptr) {
3598 } else if (!handle->orchestrator) {
3600 } else {
3601 std::string snapshot =
3602 handle->orchestrator->residency_snapshot_json();
3603 *out_json = alloc_cstr(snapshot);
3604 if (*out_json == nullptr) {
3606 }
3607 }
3608 return rc;
3609}
3610
3611} // extern "C"
gh#32 (v2.1.6) resume
static std::string validate(const std::string &gbnf_content)
Validate a GBNF grammar string.
gh#59 (v2.3.1): RAII guard combining api_mutex + log scope.
Thread-safe hook registration and dispatch.
int fire_pre(entropic_hook_point_t point, const char *context_json, char **out_json)
Fire pre-hooks.
void fire_post(entropic_hook_point_t point, const char *context_json, char **out_json)
Fire post-hooks.
void fire_info(entropic_hook_point_t point, const char *context_json)
Fire informational hooks (no modify, no cancel).
Concrete base class for inference backends (80% logic).
Definition backend.h:69
void unload()
Full unload (→ COLD).
Definition backend.cpp:139
LlamaCppBackend — common llama.cpp patterns (15% layer).
Multi-model lifecycle and routing orchestrator.
std::function< void(ResidencyEvent event, const std::string &tier_name, const std::string &model_path, size_t footprint)> ResidencyObserverFn
Residency observer callback type (internal C++ form).
void clear_all_prompt_caches()
Invalidate prompt/KV caches across every pooled backend.
ResidencyEvent
Residency observer event codes — mirror the C ABI enum entropic_residency_event_t exactly (LOADED=0,...
Manages MCP server instances and routes tool calls.
void interrupt_external_tools()
Abort in-flight tool calls across every external MCP client.
SQLite-based storage backend.
Definition backend.h:43
bool save_messages(const std::string &conversation_id, const std::string &messages_json)
Save messages to a conversation.
Definition backend.cpp:225
bool complete_delegation(const std::string &delegation_id, const std::string &status, const std::optional< std::string > &result_summary=std::nullopt)
Mark a delegation as completed or failed.
Definition backend.cpp:602
bool create_delegation(const std::string &parent_conversation_id, const std::string &delegating_tier, const std::string &target_tier, const std::string &task, int max_turns, std::string &delegation_id, std::string &child_conversation_id)
Create a delegation record with a child conversation.
Definition backend.cpp:540
bool save_snapshot(const std::string &conversation_id, const std::string &messages_json)
Save a pre-compaction snapshot of full conversation history.
Definition backend.cpp:774
std::string create_conversation(const std::string &title="New Conversation", const std::optional< std::string > &project_path=std::nullopt, const std::optional< std::string > &model_id=std::nullopt)
Create a new conversation.
Definition backend.cpp:131
std::string to_json(size_t count) const
Serialize recent entries to JSON array string.
Processes tool calls from model output.
const ToolCallHistory & tool_history() const
Access the tool-call history ring buffer.
std::string auto_discover_and_load()
Auto-discover and load bundled_models.yaml.
Private definition of the entropic_engine struct.
static entropic_error_t check_mcp_auth(entropic_handle_t h)
Check handle prerequisites for MCP auth APIs.
Definition entropic.cpp:119
static void apply_identity_frontmatter(entropic_handle_t h, const std::string &name, const entropic::prompts::IdentityFrontmatter &fm)
Cache per-tier frontmatter fields (allowed_tools, validation_rules, relay).
Definition entropic.cpp:762
entropic_error_t entropic_run_messages(entropic_handle_t handle, const char *messages_json, char **result_json)
Blocking multimodal agentic run (gh#37, v2.1.8).
entropic_error_t entropic_identity_count(entropic_handle_t handle, size_t *total, size_t *dynamic)
Get identity count (total and dynamic).
static bool si_create_delegation(const char *parent_id, const char *delegating_tier, const char *target_tier, const char *task, int max_turns, std::string &delegation_id, std::string &child_conversation_id, void *user_data)
Initialize persistence: storage + session logger.
Definition entropic.cpp:492
static entropic_error_t reject_if_configured(entropic_handle_t h)
Post-parse config setup: subsystem construction + wiring.
static std::vector< std::string > resolve_allowed_tools(entropic_engine *h, const std::string &tier)
Post-parse config setup: load bundled models, set configured.
Definition entropic.cpp:250
static void start_external_bridge(entropic_handle_t h)
Start the external MCP bridge if enabled in config.
static char * sp_search_delegations(const char *query, int max_results, void *ud)
State provider: search_delegations (gh#32, v2.1.6).
entropic_error_t entropic_grammar_deregister(entropic_handle_t handle, const char *key)
Remove a grammar from the registry.
entropic_error_t entropic_context_usage(entropic_handle_t handle, size_t *tokens_used, size_t *capacity)
Read current context-window pressure (gh#39, v2.1.8).
char * entropic_adapter_list(entropic_handle_t handle)
List all known adapters as a JSON array.
entropic_error_t entropic_validation_resume_retry(entropic_handle_t handle)
Resume a paused constitutional revision pass.
static char * sp_load_delegation_conversation(const char *delegation_id, void *ud)
State provider: load_delegation_conversation (gh#32, v2.1.6).
static entropic_error_t run_messages_stream_inner(entropic_handle_t handle, const char *messages_json, void(*on_token)(const char *token, size_t len, void *user_data), void *user_data, int *cancel_flag)
Streaming multimodal run (gh#37, v2.1.8).
static char * sp_get_metrics(void *ud)
State provider: get_metrics.
static char * sp_get_state(void *ud)
State provider: get_state (runtime environment).
static void rewire_observers(entropic_handle_t h)
Re-bind every pre-configure observer to the new engine.
Definition entropic.cpp:431
entropic_error_t entropic_deserialize_mcp_keys(entropic_handle_t handle, const char *json)
Deserialize all identity key sets from JSON.
static char * sp_get_residency(void *ud)
State provider: get_residency — VRAM residency snapshot.
static std::string build_shared_prompt_prefix(entropic_handle_t h, const std::filesystem::path &data_dir)
Build shared system prompt prefix (constitution + app_context).
Definition entropic.cpp:731
entropic_error_t entropic_create(entropic_handle_t *handle)
Create a new engine instance.
Definition entropic.cpp:181
static void init_mcp_servers(entropic_handle_t h, const std::filesystem::path &data_dir)
Initialize MCP servers with resolved working directory.
Definition entropic.cpp:711
entropic_error_t entropic_grammar_register(entropic_handle_t handle, const char *key, const char *gbnf_content)
Register a grammar by key with GBNF content.
entropic_error_t entropic_set_stream_observer(entropic_handle_t handle, void(*observer)(const char *token, size_t len, void *user_data), void *user_data)
Set a global stream observer callback.
entropic_error_t entropic_validation_set_auto_retry(entropic_handle_t handle, int enabled)
Toggle automatic constitutional revision.
entropic_error_t entropic_update_identity(entropic_handle_t handle, const char *name, const char *config_json)
Update an existing dynamic identity.
entropic_error_t entropic_set_residency_observer(entropic_handle_t handle, entropic_residency_observer_t observer, void *user_data)
Register a residency observer on the orchestrator.
entropic_error_t entropic_metrics_json(entropic_handle_t handle, char **out)
Get loop metrics as JSON (flat + per_tier).
entropic_error_t entropic_validation_set_identity(entropic_handle_t handle, const char *identity_name, bool enabled)
Set per-identity validation override.
entropic_error_t entropic_grant_mcp_key_from(entropic_handle_t handle, const char *granter, const char *grantee, const char *pattern, entropic_mcp_access_level_t level)
Grant a key from one identity to another.
void entropic_free(void *ptr)
Free memory allocated by the engine.
entropic_error_t entropic_run_streaming(entropic_handle_t handle, const char *input, void(*on_token)(const char *token, size_t len, void *user_data), void *user_data, int *cancel_flag)
Streaming generation — delegates entirely to engine.
double entropic_throughput_tok_per_sec(entropic_handle_t handle, const char *model_path)
Get EWMA throughput estimate in tokens per second.
char * entropic_profile_get(entropic_handle_t handle, const char *name)
Get a GPU resource profile by name as JSON.
entropic_error_t entropic_validation_accept_last(entropic_handle_t handle)
Accept the last paused attempt as the final answer.
static void populate_tier_info(entropic_handle_t h, const std::filesystem::path &data_dir, const std::string &shared_prefix)
Register per-tier ChildContextInfo with the engine.
Definition entropic.cpp:451
static void init_persistence(entropic_handle_t h)
Initialize persistence: storage + session logger + StorageInterface.
Definition entropic.cpp:651
void entropic_throughput_reset(entropic_handle_t handle, const char *model_path)
Reset throughput tracking data.
static char * sp_get_docs(const char *section, void *ud)
State provider: get_docs.
static bool si_save_conversation(const char *conversation_id, const char *messages_json, void *user_data)
StorageInterface bridge: save_conversation trampoline.
Definition entropic.cpp:550
static void rewire_state_observer(entropic_handle_t h)
Propagate any pre-configure state observer to the new engine.
Definition entropic.cpp:391
entropic_error_t entropic_set_critique_callbacks(entropic_handle_t handle, void(*start_cb)(void *user_data), void(*end_cb)(void *user_data), void *user_data)
Register critique start/end callbacks on the handle (gh#50, v2.1.12).
entropic_error_t entropic_queue_user_message(entropic_handle_t handle, const char *message)
Enqueue a follow-up user message while a run is in flight.
char * entropic_list_mcp_keys(entropic_handle_t handle, const char *identity_name)
List MCP keys for an identity as JSON array.
void entropic_destroy(entropic_handle_t handle)
Destroy an engine instance.
entropic_error_t entropic_adapter_load(entropic_handle_t handle, const char *adapter_name, const char *adapter_path, const char *base_model_path, float scale)
Load a LoRA adapter into RAM.
entropic_error_t entropic_validation_set_enabled(entropic_handle_t handle, bool enabled)
Enable or disable constitutional validation globally.
entropic_error_t entropic_configure_from_file(entropic_handle_t handle, const char *config_path)
Configure the engine from a YAML config file.
int entropic_adapter_state(entropic_handle_t handle, const char *adapter_name)
Query adapter lifecycle state.
static void rewire_critique_callbacks(entropic_handle_t h)
Propagate pre-configure critique callbacks to a newly- constructed ConstitutionalValidator (gh#50,...
Definition entropic.cpp:411
int64_t entropic_seconds_since_last_activity(entropic_handle_t handle)
gh#35: idle-time accessor for host-side idle-exit policies.
entropic_error_t entropic_set_state_observer(entropic_handle_t handle, void(*observer)(int state, void *user_data), void *user_data)
Register a state-change observer on the handle.
static void init_engine_and_interfaces(entropic_handle_t h, const std::filesystem::path &data_dir)
Build the engine + inference interfaces (configure step 2).
static void wire_hooks_and_validator(entropic_handle_t h, entropic::InferenceInterface &iface, const std::string &constitution_text)
Wire hook dispatch and attach the constitutional validator.
Definition entropic.cpp:935
entropic_error_t entropic_grant_mcp_key(entropic_handle_t handle, const char *identity_name, const char *pattern, entropic_mcp_access_level_t level)
Grant an MCP tool key to an identity.
static entropic::LoopConfig build_loop_config(entropic_handle_t h)
Build LoopConfig from parsed config.
static entropic_error_t check_orchestrator(entropic_handle_t h)
Check handle prerequisites for orchestrator APIs.
Definition entropic.cpp:104
entropic_error_t entropic_context_count(entropic_handle_t handle, size_t *count)
Get conversation message count.
static void rewire_queue_observer(entropic_handle_t h)
Propagate any pre-configure queue observer to the new engine.
Definition entropic.cpp:372
char * entropic_validation_last_result(entropic_handle_t handle)
Get last validation result as JSON.
static char * sp_get_validation(void *ud)
Return the validator's last verdict as JSON for ON_COMPLETE.
Definition entropic.cpp:877
char * entropic_grammar_validate(const char *gbnf_content)
Validate a GBNF grammar string without registering.
char * entropic_profile_list(entropic_handle_t handle)
List all registered profile names as a JSON array.
entropic_error_t entropic_compute_perplexity(entropic_handle_t handle, const char *model_id, const int32_t *tokens, int n_tokens, float *perplexity)
Compute perplexity for a token sequence.
entropic_error_t entropic_grammar_register_file(entropic_handle_t handle, const char *key, const char *path)
Register a grammar from a GBNF file.
static char * sp_get_config(void *ud)
State provider: get_config.
Definition entropic.cpp:984
entropic_error_t entropic_speculative_compat(entropic_handle_t handle, int *compatible, char **diagnostic)
Query speculative-decoding compatibility for the configured target/draft pair.
entropic_error_t entropic_destroy_identity(entropic_handle_t handle, const char *name)
Destroy a dynamic identity.
static entropic::InferenceBackend * require_active_backend(entropic_handle_t h, const char *tier_name)
Resolve tier name to an ACTIVE backend, or throw.
Definition entropic.cpp:153
static char * sp_get_history(int max_entries, void *ud)
State provider: get_history — conversation context snapshot.
int entropic_check_mcp_key(entropic_handle_t handle, const char *identity_name, const char *tool_name, entropic_mcp_access_level_t level)
Check MCP key authorization for an identity.
entropic_error_t entropic_run(entropic_handle_t handle, const char *input, char **result_json)
Single-turn blocking agentic run.
static void rewire_stream_observer(entropic_handle_t h)
Propagate any pre-configure stream observer to the new engine.
Definition entropic.cpp:353
char * entropic_adapter_info(entropic_handle_t handle, const char *adapter_name)
Get adapter info as JSON string.
static char * sp_get_tools(void *ud)
State provider: get_tools.
entropic_error_t entropic_configure(entropic_handle_t handle, const char *config_json)
Configure the engine from a JSON/YAML config string.
entropic_error_t entropic_profile_deregister(entropic_handle_t handle, const char *name)
Remove a GPU resource profile by name.
entropic_error_t entropic_set_queue_observer(entropic_handle_t handle, void(*observer)(const char *, size_t, void *), void *user_data)
Register the queue-consumption observer.
static std::vector< entropic::Message > parse_and_check_vision(entropic_handle_t handle, const char *messages_json, entropic_error_t &out_rc)
Parse messages_json and check vision-tier availability (gh#37/gh#41).
static char * tool_history_json_thunk(size_t count, void *ud)
Wire the ToolExecutor and attach it to the engine.
Definition entropic.cpp:821
static std::string build_assembled_prompt_for_tier(entropic_engine *h, const std::string &tier_name)
Build the assembled system prompt the engine would send for a given tier (constitution + app_context ...
static bool si_complete_delegation(const char *delegation_id, const char *status, const char *summary, void *user_data)
StorageInterface bridge: complete_delegation trampoline.
Definition entropic.cpp:534
entropic_error_t entropic_user_message_queue_depth(entropic_handle_t handle, size_t *count)
Snapshot the mid-gen queue depth.
static entropic_error_t init_orchestrator(entropic_handle_t h, const std::filesystem::path &data_dir)
Shared body of all entropic_configure* entry points.
char * entropic_grammar_get(entropic_handle_t handle, const char *key)
Get grammar GBNF content by key.
char * entropic_list_identities(entropic_handle_t handle)
List all identity names as JSON array.
entropic_error_t entropic_context_get(entropic_handle_t handle, char **messages_json)
Get conversation as JSON array.
static void wire_prompts_and_persistence(entropic_handle_t h, const std::filesystem::path &data_dir)
Assemble prompts + wire validation/persistence (config step 3).
static entropic_error_t configure_common(entropic_handle_t h)
Shared body of all entropic_configure* entry points.
entropic_error_t entropic_profile_register(entropic_handle_t handle, const char *profile_json)
Register a custom GPU resource profile from JSON.
char * entropic_serialize_mcp_keys(entropic_handle_t handle)
Serialize all identity key sets to JSON.
void entropic_free_logprob_result(entropic_logprob_result_t *result)
Free internal arrays of a logprob result.
entropic_error_t entropic_adapter_swap(entropic_handle_t handle, const char *adapter_name)
Swap active LoRA adapter.
static entropic_error_t run_messages_inner(entropic_handle_t handle, const char *messages_json, char **result_json)
Blocking multimodal run (gh#37, v2.1.8).
entropic_error_t entropic_set_delegation_callbacks(entropic_handle_t handle, ent_delegation_start_cb on_start, ent_delegation_complete_cb on_complete, void *user_data)
Register delegation start/complete callbacks (gh#29, v2.1.5).
entropic_error_t entropic_interrupt(entropic_handle_t handle)
Interrupt a running generation (thread-safe).
static std::vector< std::string > collect_delegatable_tiers(const entropic::ParsedConfig &config)
Collect valid delegation targets from handoff_rules.
Definition entropic.cpp:688
static std::vector< std::string > filter_tools(const nlohmann::json &all_tools, const std::vector< std::string > &allowed)
Filter tool definitions by allowed list.
Definition entropic.cpp:276
int entropic_model_has_vision(entropic_handle_t handle, const char *model_id)
Check if a model has vision (multimodal) capability.
entropic_error_t entropic_get_diagnostic_prompt(entropic_handle_t handle, char **prompt_out)
Get diagnostic prompt text for /diagnose command (stub).
static void wire_external_interrupt(entropic_handle_t h)
Wire engine interrupt propagation into MCP transports (P1-10).
Definition entropic.cpp:333
static entropic::StorageInterface build_storage_iface(entropic::SqliteStorageBackend *sb)
Build a populated StorageInterface bound to sb.
Definition entropic.cpp:633
static void cache_tier_allowed_tools(entropic_handle_t h, const std::filesystem::path &data_dir)
Cache per-tier frontmatter fields from identity files.
Definition entropic.cpp:784
const char * entropic_last_error(entropic_handle_t handle)
Read the per-handle last_error under api_mutex.
Definition entropic.cpp:90
entropic_error_t entropic_configure_dir(entropic_handle_t handle, const char *project_dir)
Configure using layered resolution (project dir).
entropic_error_t entropic_residency_snapshot(entropic_handle_t handle, char **out_json)
Return the engine's current residency-set snapshot as JSON.
entropic_error_t entropic_run_messages_streaming(entropic_handle_t handle, const char *messages_json, void(*on_token)(const char *token, size_t len, void *user_data), void *user_data, int *cancel_flag)
Streaming multimodal agentic run (gh#37, v2.1.8).
entropic_error_t entropic_context_clear(entropic_handle_t handle)
Clear conversation history.
static char * sp_get_identities(void *ud)
State provider: get_identities.
entropic_error_t entropic_create_identity(entropic_handle_t handle, const char *config_json)
Create a dynamic identity from JSON config.
entropic_error_t entropic_set_attempt_boundary_cb(entropic_handle_t handle, ent_validation_attempt_boundary_cb cb, void *user_data)
Register attempt-boundary callback on the validator.
int entropic_api_version(void)
Get the plugin API version number.
char * entropic_grammar_list(entropic_handle_t handle)
List all registered grammars as a JSON array.
static bool si_create_conversation(const char *title, std::string &conversation_id, void *user_data)
StorageInterface bridge: create_conversation trampoline.
Definition entropic.cpp:519
const char * entropic_version(void)
Get the library version string.
static entropic_error_t check_identity(entropic_handle_t h)
Check handle prerequisites for identity manager APIs.
Definition entropic.cpp:134
static void wire_state_provider(entropic_handle_t h)
Wire state provider to the EntropicServer.
static int facade_get_tool_prompt(const char *tier, char **result, void *user_data)
Build formatted tool prompt for a tier.
Definition entropic.cpp:300
entropic_error_t entropic_get_logprobs(entropic_handle_t handle, const char *model_id, const int32_t *tokens, int n_tokens, entropic_logprob_result_t *result)
Evaluate per-token log-probabilities for a token sequence.
static void wire_tier_validation_rules(entropic_handle_t h)
Pass per-identity validation rules to the validator.
static void wire_tool_executor(entropic_handle_t h)
Wire the ToolExecutor and attach it to the engine.
Definition entropic.cpp:845
entropic_error_t entropic_adapter_unload(entropic_handle_t handle, const char *adapter_name)
Unload a LoRA adapter.
static bool si_save_snapshot(const char *conversation_id, const char *messages_json, void *user_data)
StorageInterface bridge: save_snapshot trampoline.
Definition entropic.cpp:566
void * entropic_alloc(size_t size)
Allocate memory using the engine's allocator.
entropic_error_t entropic_clear_user_message_queue(entropic_handle_t handle)
Drop all queued mid-gen user messages.
entropic_error_t entropic_revoke_mcp_key(entropic_handle_t handle, const char *identity_name, const char *pattern)
Revoke an MCP tool key from an identity.
char * entropic_get_identity_config(entropic_handle_t handle, const char *name)
Get identity config as JSON by name.
static char * alloc_cstr(const char *src)
Allocate a C string copy via the engine allocator.
Definition entropic.cpp:46
static bool si_load_delegation_with_messages(const char *delegation_id, std::string &result_json, void *user_data)
StorageInterface bridge: load_delegation_with_messages.
Definition entropic.cpp:587
Public C API for the Entropic inference engine.
ent_decision_t(* ent_delegation_start_cb)(const ent_delegation_request_t *req, void *user_data)
Callback fired before a delegation runs.
Definition entropic.h:931
ent_decision_t(* ent_delegation_complete_cb)(const ent_delegation_result_t *res, void *user_data)
Callback fired after a delegation produces a patch.
Definition entropic.h:956
void(* entropic_residency_observer_t)(entropic_residency_event_t event, const char *tier_name, const char *model_path, size_t footprint_bytes, void *user_data)
Residency observer callback.
Definition entropic.h:678
entropic_residency_event_t
Reasons fired by entropic_residency_observer_t.
Definition entropic.h:653
entropic_mcp_access_level_t
Access level enum for MCP authorization.
Definition entropic.h:1617
void(* ent_validation_attempt_boundary_cb)(int attempt_n, void *user_data)
Stream-side callback fired between constitutional revision passes.
Definition entropic.h:1003
Entropic MCP server — engine-level tools including introspection.
entropic_error_t
Error codes returned by all C API functions.
Definition error.h:35
@ ENTROPIC_OK
Success.
Definition error.h:36
@ ENTROPIC_ERROR_NO_VISION_TIER
Image content present but no vision-capable tier (v2.1.8, gh#41)
Definition error.h:86
@ ENTROPIC_ERROR_ADAPTER_SWAP_FAILED
Swap failed (e.g., base model not ACTIVE) (v1.9.2)
Definition error.h:65
@ ENTROPIC_ERROR_CANCELLED
Operation cancelled via cancel token.
Definition error.h:48
@ ENTROPIC_ERROR_ALREADY_EXISTS
Named resource already exists (v1.9.6)
Definition error.h:71
@ ENTROPIC_ERROR_INTERNAL
Unexpected internal error (bug)
Definition error.h:51
@ ENTROPIC_ERROR_GRAMMAR_NOT_FOUND
Grammar key not in registry (v1.9.3)
Definition error.h:67
@ ENTROPIC_ERROR_INVALID_ARGUMENT
NULL pointer, empty string, out-of-range value.
Definition error.h:37
@ ENTROPIC_ERROR_QUEUE_FULL
Mid-gen user-message queue at capacity (v2.1.10, gh#40)
Definition error.h:87
@ ENTROPIC_ERROR_INVALID_HANDLE
NULL or destroyed handle (v1.8.9)
Definition error.h:55
@ ENTROPIC_ERROR_OUT_OF_MEMORY
Allocation failed (system RAM or VRAM)
Definition error.h:49
@ ENTROPIC_ERROR_EVAL_FAILED
Evaluation failed (llama_decode error) (v1.9.10)
Definition error.h:79
@ ENTROPIC_ERROR_ADAPTER_LOAD_FAILED
LoRA file invalid or incompatible with base model (v1.9.2)
Definition error.h:64
@ ENTROPIC_ERROR_PROFILE_NOT_FOUND
Profile name not in registry (v1.9.7)
Definition error.h:73
@ ENTROPIC_ERROR_IO
File/network I/O error.
Definition error.h:50
@ ENTROPIC_ERROR_GENERATE_FAILED
Generation failed (context overflow, model error)
Definition error.h:42
@ ENTROPIC_ERROR_INVALID_CONFIG
Config validation failed (missing fields, bad values)
Definition error.h:38
@ ENTROPIC_ERROR_INVALID_STATE
Operation not valid in current state (e.g., generate before activate)
Definition error.h:39
@ ENTROPIC_ERROR_LOAD_FAILED
Model load failed (corrupt file, OOM, unsupported format)
Definition error.h:41
entropic_hook_point_t
Hook points in the engine lifecycle.
Definition hooks.h:34
Pure C interface contract for inference backends.
void entropic_inference_log_silence(void)
Silence all llama/ggml log output.
Factory for building InferenceInterface from a ModelOrchestrator.
JSON serialization helpers for the facade.
LlamaCppBackend — llama.cpp C API integration.
Config loader — YAML to C++ structs with validation.
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
Prompt manager — frontmatter parsing, identity loading, assembly.
Shared parser: messages-JSON wire format → vector<Message>.
@ VISION
Vision / multimodal input (v1.9.11)
bool any_message_has_images(const std::vector< Message > &messages)
Convenience: true if any message carries image content_parts.
@ passed_consumer_override
gh#30 (v2.1.5): consumer called accept_last() to override a paused rejection.
@ rejected_reverted_length
Revision gutted content >50%; original preserved.
@ passed
No violations, content unchanged.
@ revised
Violations found; revision applied.
@ paused_pending_consumer
gh#30 (v2.1.5): auto_retry disabled and a critique failed.
@ skipped
Validation did not run (skip_tiers / pure-tool-call / empty)
@ rejected_max_revisions
Revisions exhausted; last output returned as-is.
@ DYNAMIC
Created at runtime via API.
@ STATIC
Loaded from YAML frontmatter file at startup.
void destroy_orchestrator_interface(InterfaceContext *context)
Free a context returned by build_orchestrator_interface().
std::vector< Message > parse_messages_json(const char *json_str)
Parse a JSON array of messages into a vector of Message.
MCPAccessLevel
MCP tool access level for per-identity authorization.
Definition config.h:38
@ COLD
Not loaded. No resources consumed.
InferenceInterface build_orchestrator_interface(ModelOrchestrator *orchestrator, const std::string &default_tier, InterfaceContext **out_context)
Build an InferenceInterface wired to an orchestrator.
ModelOrchestrator — multi-model lifecycle and routing.
Resolved tier information for building child delegation contexts.
int max_revisions
Max re-generation attempts (0 = critique only)
Definition config.h:567
bool enabled
Global enable/disable (default OFF)
Definition config.h:566
bool enabled
Enable external MCP.
Definition config.h:424
Named GPU resource profile for controlling inference hardware knobs.
Definition config.h:215
int n_threads_batch
CPU threads for batch processing (0 = use n_threads)
Definition config.h:219
int n_batch
Batch size for prompt processing (1-2048)
Definition config.h:217
std::string name
Profile name ("maximum", "balanced", "background", "minimal")
Definition config.h:216
int n_threads
CPU threads for generation (0 = auto-detect)
Definition config.h:218
std::string description
Human-readable description.
Definition config.h:220
Full identity configuration.
Definition identity.h:48
std::string name
Unique identity name (e.g., "eng", "npc_blacksmith")
Definition identity.h:49
std::vector< std::string > focus
Classification focus keywords (min 1)
Definition identity.h:51
IdentityOrigin origin
How this identity was created.
Definition identity.h:67
std::string system_prompt
Full system prompt text (markdown body)
Definition identity.h:50
Configuration for the identity manager.
Configuration for the agentic loop.
int context_length
Context budget for compaction (v2.0.4)
bool stream_output
Stream vs batch generation.
bool auto_approve_tools
Skip tool approval (v1.8.5)
Mutable state carried through the agentic loop.
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
std::unordered_map< std::string, TierConfig > tiers
Tier name → config.
Definition config.h:357
std::string find_tier_by_path(const std::filesystem::path &model_path) const
Find tier name by model path.
Definition config.h:372
std::string default_tier
Default tier name.
Definition config.h:359
Full parsed configuration.
Definition config.h:714
PermissionsConfig permissions
Tool permissions.
Definition config.h:718
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
ModelsConfig models
Tiers + router.
Definition config.h:715
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
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 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
bool auto_approve
Skip confirmation prompts.
Definition config.h:403
std::unordered_map< std::string, std::vector< std::string > > handoff_rules
Tier handoff rules.
Definition config.h:393
Storage interface for conversation persistence.
bool(* create_conversation)(const char *title, std::string &conversation_id, void *user_data)
Create a root conversation row.
Tool execution interface for the engine.
void(* free_fn)(char *)
Free function for strings returned by history_json.
ToolExecutionFn process_tool_calls
Dispatches tool calls.
void * user_data
Opaque pointer (ToolExecutor*)
char *(* history_json)(size_t count, void *user_data)
Optional: return a compact JSON summary of recent tool calls (for validator retry enrichment / diagno...
Identity frontmatter — full tier identity metadata.
Definition manager.h:65
std::vector< std::string > validation_rules
Per-identity constitutional rules (v2.0.6)
Definition manager.h:83
std::optional< std::vector< std::string > > allowed_tools
Tool filter.
Definition manager.h:74
bool relay_single_delegate
Skip re-synthesis when single delegate returns (v2.0.11)
Definition manager.h:84
Parsed identity file: frontmatter + body.
Definition manager.h:105
Engine handle struct — owns all subsystems.
std::unique_ptr< entropic::ConstitutionalValidator > validator
Constitutional validation.
std::unique_ptr< entropic::ToolExecutor > tool_executor
Tool dispatch.
void(* critique_end_cb)(void *)
Fires after the critique generate returns.
int log_id
gh#59 (v2.3.1): unique handle id for per-handle log routing via entropic::log::HandleAwareSink.
std::unordered_map< std::string, std::vector< std::string > > tier_validation_rules
Per-tier validation_rules from identity frontmatter (v2.0.6).
std::unique_ptr< entropic::SqliteStorageBackend > storage
SQLite persistence.
entropic::InferenceInterface inference_iface
Stable copy for validator lifetime.
std::unique_ptr< entropic::SessionLogger > session_logger
Model transcript log.
std::unordered_map< std::string, std::vector< std::string > > tier_allowed_tools
Per-tier allowed_tools from identity frontmatter.
std::atomic< bool > configured
True after configure()
std::unique_ptr< entropic::ExternalBridge > external_bridge
Unix socket MCP bridge.
void(* state_observer)(int, void *)
Observer for engine state transitions.
entropic::config::BundledModels bundled_models
Model registry.
void * stream_observer_data
Observer user_data.
std::unique_ptr< entropic::IdentityManager > identity_manager
Identity lifecycle.
std::string last_error
Per-handle error message.
std::unique_ptr< entropic::AgentEngine > engine
Agentic loop (owns conversation state)
void * critique_cb_data
Forwarded to both callbacks.
std::unique_ptr< entropic::MCPAuthorizationManager > mcp_auth
Per-identity tool auth.
std::unique_ptr< entropic::ModelOrchestrator > orchestrator
Model pool + routing.
entropic::ParsedConfig config
Parsed config.
void(* stream_observer)(const char *, size_t, void *)
Global stream observer — fires for all streaming output.
void(* queue_observer)(const char *, size_t, void *)
Observer fired when a queued mid-gen user message is consumed and seeded as the next turn.
std::unique_ptr< entropic::ServerManager > server_manager
MCP server lifecycle.
entropic::InterfaceContext * inference_iface_ctx
Per-handle owned context backing inference_iface.user_data.
void(* critique_start_cb)(void *)
Fires before the constitutional validator's critique generate begins.
entropic::HookRegistry hook_registry
Hook dispatch.
Per-token log-probability result.
Definition entropic.h:2077
float perplexity
exp(-mean(logprobs)) over the sequence.
Definition entropic.h:2080
int32_t * tokens
Input tokens echoed back (N values).
Definition entropic.h:2079
int n_logprobs
Number of logprob values (n_tokens - 1).
Definition entropic.h:2083
int n_tokens
Number of input tokens.
Definition entropic.h:2082
float * logprobs
Per-token log-probabilities (N-1 values).
Definition entropic.h:2078
float total_logprob
Sum of all logprob values.
Definition entropic.h:2081
Read-only engine state provider for introspection tools.
char *(* get_config)(void *user_data)
Get current engine configuration as JSON.
UTF-8-boundary-aware string truncation helper for the facade.