Entropic 2.11.1
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 "../inference/tool_call_serialize.h" // gh#98: shared typed serializer
28
29#include <nlohmann/json.hpp>
30
31#include <atomic>
32
33#include <cstdlib>
34#include <fstream>
35#include <vector>
36#include <cstring>
37#include <filesystem>
38#include <new>
39#include <stdexcept>
40#include <string>
41
42static auto s_log = entropic::log::get("facade");
43
74template <typename Fn>
77 try {
78 rc = fn();
79 } catch (const std::filesystem::filesystem_error& e) {
80 if (handle) { handle->last_error = e.what(); }
81 s_log->error("c_api filesystem_error: {}", e.what());
83 } catch (const nlohmann::json::exception& e) {
84 if (handle) { handle->last_error = e.what(); }
85 s_log->error("c_api json::exception: {}", e.what());
87 } catch (const std::exception& e) {
88 if (handle) { handle->last_error = e.what(); }
89 s_log->error("c_api std::exception: {}", e.what());
91 }
92 return rc;
93}
94
106static char* alloc_cstr(const char* src) {
107 if (!src) { return nullptr; }
108 size_t len = std::strlen(src) + 1;
109 auto* dst = static_cast<char*>(entropic_alloc(len));
110 if (dst) { std::memcpy(dst, src, len); }
111 return dst;
112}
113
121static char* alloc_cstr(const std::string& src) {
122 return alloc_cstr(src.c_str());
123}
124
125/* setup_ggml_logging moved to ModelOrchestrator::initialize() — Step 7 */
126
127// gh#58 follow-up (v2.2.6): per-handle last_error. Pre-v2.2.6 the
128// public API in src/types/error.cpp ignored the handle parameter and
129// returned a single thread-local buffer, so every handle->last_error
130// assignment in the facade was unreadable from the consumer side.
131// Thread-local cache here means the returned const char* stays valid
132// until the *same* thread calls entropic_last_error again — matching
133// the documented v1.8.0 contract.
134static thread_local std::string s_last_error_cache;
135static thread_local char s_pre_create_error[512] = "";
136
155extern "C" const char* entropic_last_error(entropic_handle_t handle) {
156 if (!handle) { return s_pre_create_error; }
157 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
158 s_last_error_cache = handle->last_error;
159 return s_last_error_cache.c_str();
160}
161
170 if (!h) { return ENTROPIC_ERROR_INVALID_HANDLE; }
171 if (!h->configured.load() || !h->orchestrator) {
173 }
174 return ENTROPIC_OK;
175}
176
185 if (!h) { return ENTROPIC_ERROR_INVALID_HANDLE; }
186 if (!h->configured.load() || !h->mcp_auth) {
188 }
189 return ENTROPIC_OK;
190}
191
200 if (!h) { return ENTROPIC_ERROR_INVALID_HANDLE; }
201 if (!h->configured.load() || !h->identity_manager) {
203 }
204 return ENTROPIC_OK;
205}
206
207/* find_tier_by_model_path moved to ModelsConfig::find_tier_by_path() — v2.0.1 */
208
219 entropic_handle_t h, const char* tier_name)
220{
221 auto* backend = h->orchestrator->get_backend(tier_name);
222 if (!backend) {
223 throw std::runtime_error(
224 "no backend for tier: " + std::string(tier_name));
225 }
226 if (!backend->is_active()) {
227 throw std::runtime_error(
228 "model not active: " + std::string(tier_name));
229 }
230 return backend;
231}
232
233extern "C" {
234
249 if (handle == nullptr) {
251 }
252 entropic::log::init(spdlog::level::info);
253 entropic_inference_log_silence(); // silent until configure enables
254
255 auto* engine = new (std::nothrow) entropic_engine();
256 if (engine == nullptr) {
257 *handle = nullptr;
259 }
260
261 // gh#59 (v2.3.1): assign a monotonic log id. Used by the
262 // HandleAwareSink dispatcher to route session.log writes to the
263 // right file when multiple handles coexist. 0 is reserved for
264 // "no handle scope active."
265 static std::atomic<int> s_log_id_counter{0};
266 engine->log_id = ++s_log_id_counter;
267
268 s_log->info("entropic_create() — v{} (log_id={})",
269 CONFIG_ENTROPIC_VERSION_STRING, engine->log_id);
270
271 *handle = engine;
272 return ENTROPIC_OK;
273}
274
293/* preload_bundled_models moved to BundledModels::auto_discover_and_load() — Step 6 */
294
295/* InferenceInterface wiring moved to inference/interface_factory.cpp — Step 3 */
296
297/* InferenceInterface C-wrappers + factory moved to inference/interface_factory.cpp — Step 3 */
298
299/* facade_process_directives → engine->build_directive_hooks() — v2.0.2 */
300/* facade_resolve_tier/tier_exists/handoff/param → engine->set_tier_info() — v2.0.2 */
301/* wire_engine_interfaces → inline in configure_common — v2.0.2 */
302
303// ── Tool prompt injection (v2.0.4) ─────────────────────────
304
317static std::vector<std::string> resolve_allowed_tools(
318 entropic_engine* h, const std::string& tier) {
319 // Primary: cached from identity frontmatter (v2.0.4)
320 auto cached = h->tier_allowed_tools.find(tier);
321 if (cached != h->tier_allowed_tools.end()) {
322 return cached->second;
323 }
324 // Fallback: dynamic identity or model config
325 if (h->identity_manager) {
326 auto* cfg = h->identity_manager->get(tier);
327 if (cfg && !cfg->allowed_tools.empty()) {
328 return cfg->allowed_tools;
329 }
330 }
331 return {};
332}
333
343static std::vector<std::string> filter_tools(
344 const nlohmann::json& all_tools,
345 const std::vector<std::string>& allowed) {
346 std::vector<std::string> result;
347 for (const auto& tool : all_tools) {
348 std::string name = tool.value("name", "");
349 bool pass = allowed.empty()
350 || std::find(allowed.begin(), allowed.end(), name)
351 != allowed.end();
352 if (pass) { result.push_back(tool.dump()); }
353 }
354 return result;
355}
356
375static int facade_get_tool_prompt(const char* tier, char** result,
376 void* user_data) {
377 auto* h = static_cast<entropic_engine*>(user_data);
378 *result = nullptr;
379 if (!h || !h->server_manager) { return 1; }
380
381 std::string tier_name = tier ? tier : "";
382 auto all_json = h->server_manager->list_tools();
383 auto all_tools = nlohmann::json::parse(all_json, nullptr, false);
384 auto allowed = resolve_allowed_tools(h, tier_name);
385 auto tool_jsons = filter_tools(all_tools, allowed);
386 if (!all_tools.is_array() || all_tools.empty() || tool_jsons.empty()) {
387 return 1;
388 }
389
390 nlohmann::json arr = nlohmann::json::array();
391 for (const auto& tj : tool_jsons) {
392 auto obj = nlohmann::json::parse(tj, nullptr, false);
393 if (!obj.is_discarded()) { arr.push_back(std::move(obj)); }
394 }
395 *result = strdup(arr.dump().c_str());
396 return 0;
397}
398
412 h->engine->set_external_interrupt(
413 [](void* ud) {
414 auto* sm = static_cast<entropic::ServerManager*>(ud);
415 if (sm) { sm->interrupt_external_tools(); }
416 }, h->server_manager.get());
417}
418
432 if (h->stream_observer != nullptr && h->engine) {
433 h->engine->set_stream_observer(
435 }
436}
437
451 if (h->queue_observer != nullptr && h->engine) {
452 h->engine->set_queue_observer(
453 h->queue_observer, h->queue_observer_data);
454 }
455}
456
470 if (h->state_observer != nullptr && h->engine) {
471 h->engine->set_state_observer(
472 h->state_observer, h->state_observer_data);
473 }
474}
475
490 if ((h->critique_start_cb != nullptr || h->critique_end_cb != nullptr)
491 && h->validator) {
492 h->validator->set_critique_callbacks(
496 }
497}
498
515
530 const std::filesystem::path& data_dir,
531 const std::string& shared_prefix) {
532 for (const auto& [name, tier] : h->config.models.tiers) {
534 info.valid = true;
535 auto parsed = entropic::prompts::resolve_tier_identity_full(
536 tier, name, data_dir);
537 info.system_prompt = shared_prefix + parsed.body;
538 info.explicit_completion = parsed.frontmatter.explicit_completion
539 .value_or(!tier.auto_chain.has_value());
540 // E6 (2.0.6-rc18): propagate per-identity caps from frontmatter
541 // so AgentEngine::tri_get_tier_param surfaces them to the loop
542 // and tool executor. -1 = "use global loop_config default".
543 info.max_iterations_override =
544 parsed.frontmatter.max_iterations;
545 info.max_tool_calls_per_turn_override =
546 parsed.frontmatter.max_tool_calls_per_turn;
547 info.max_consecutive_empty_turns_override =
548 parsed.frontmatter.max_consecutive_empty_turns;
549 info.allowed_tools = resolve_allowed_tools(h, name); // gh#121
550 h->engine->set_tier_info(name, info);
551 }
552}
553
554// ── configure_common ───────────────────────────────────────
555
575 const char* parent_id, const char* delegating_tier,
576 const char* target_tier, const char* task, int max_turns,
577 std::string& delegation_id, std::string& child_conversation_id,
578 void* user_data) {
579 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
580 if (sb == nullptr) { return false; }
581 return sb->create_delegation(
582 parent_id ? parent_id : "",
583 delegating_tier ? delegating_tier : "",
584 target_tier ? target_tier : "",
585 task ? task : "",
586 max_turns, delegation_id, child_conversation_id);
587}
588
602 const char* title, std::string& conversation_id,
603 void* user_data) {
604 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
605 if (sb == nullptr) { return false; }
606 conversation_id = sb->create_conversation(
607 title ? title : "session", std::nullopt, std::nullopt);
608 return !conversation_id.empty();
609}
610
617 const char* delegation_id, const char* status,
618 const char* summary, void* user_data) {
619 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
620 if (sb == nullptr || delegation_id == nullptr) { return false; }
621 std::optional<std::string> sum;
622 if (summary != nullptr) { sum = summary; }
623 return sb->complete_delegation(delegation_id,
624 status ? status : "completed", sum);
625}
626
633 const char* conversation_id, const char* messages_json,
634 void* user_data) {
635 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
636 if (sb == nullptr || conversation_id == nullptr
637 || messages_json == nullptr) {
638 return false;
639 }
640 return sb->save_messages(conversation_id, messages_json);
641}
642
649 const char* conversation_id, const char* messages_json,
650 void* user_data) {
651 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
652 if (sb == nullptr || conversation_id == nullptr
653 || messages_json == nullptr) {
654 return false;
655 }
656 return sb->save_snapshot(conversation_id, messages_json);
657}
658
670 const char* delegation_id, std::string& result_json,
671 void* user_data) {
672 auto* sb = static_cast<entropic::SqliteStorageBackend*>(user_data);
673 bool ok = sb != nullptr && delegation_id != nullptr;
674 std::string del_json;
675 nlohmann::json del, conv;
676 std::string child_id, target, conv_json;
677 if (ok) {
678 ok = sb->get_delegation_by_id(delegation_id, del_json);
679 }
680 if (ok) {
681 del = nlohmann::json::parse(del_json, nullptr, false);
682 ok = del.is_object();
683 }
684 if (ok) {
685 child_id = del.value("child_conversation_id", std::string{});
686 target = del.value("target_tier", std::string{});
687 ok = !child_id.empty() && !target.empty()
688 && sb->load_conversation(child_id, conv_json);
689 }
690 if (ok) {
691 conv = nlohmann::json::parse(conv_json, nullptr, false);
692 ok = conv.is_object();
693 }
694 if (ok) {
695 conv["target_tier"] = target;
696 conv["delegation_id"] = del.value("id", std::string{});
697 result_json = conv.dump();
698 }
699 return ok;
700}
701
719 si.create_delegation = si_create_delegation;
720 si.complete_delegation = si_complete_delegation;
721 si.save_conversation = si_save_conversation;
722 si.save_snapshot = si_save_snapshot;
723 si.load_delegation_with_messages = si_load_delegation_with_messages;
724 si.user_data = sb;
725 return si;
726}
727
734 if (h->config.log_dir.empty()) { return; }
735 auto db_path = h->config.log_dir / "entropic.db";
736 h->storage = std::make_unique<entropic::SqliteStorageBackend>(db_path);
737 if (h->storage->initialize()) {
738 s_log->info("storage: {}", db_path.string());
739 } else {
740 s_log->warn("storage init failed, continuing without persistence");
741 h->storage.reset();
742 }
743 // gh#32 (v2.1.6): wire StorageInterface into the engine so the
744 // create_delegation/save_conversation paths actually persist (they
745 // were dead code pre-2.1.6 because nothing populated the iface).
746 if (h->storage && h->engine) {
747 h->engine->set_storage(build_storage_iface(h->storage.get()));
748 }
749 h->session_logger = std::make_unique<entropic::SessionLogger>(
750 h->config.log_dir);
751}
752
770static std::vector<std::string> collect_delegatable_tiers(
771 const entropic::ParsedConfig& config) {
772 std::unordered_set<std::string> targets;
773 for (const auto& [source, dests] : config.routing.handoff_rules) {
774 for (const auto& t : dests) { targets.insert(t); }
775 }
776 if (targets.empty()) {
777 for (const auto& [name, tier] : config.models.tiers) {
778 if (name != config.models.default_tier) {
779 targets.insert(name);
780 }
781 }
782 }
783 return {targets.begin(), targets.end()};
784}
785
794 const std::filesystem::path& data_dir) {
795 auto root = h->config.mcp.working_dir.empty()
796 ? std::filesystem::current_path()
797 : std::filesystem::path(h->config.mcp.working_dir);
798 h->server_manager = std::make_unique<entropic::ServerManager>(
799 h->config.permissions, root);
800 auto tier_names = collect_delegatable_tiers(h->config);
801 h->server_manager->init_builtins(
802 h->config.mcp, tier_names, data_dir.string());
803
804 // gh#133 (v2.10.1): load dlopen plugins after the builtins so a plugin
805 // colliding with a built-in server name is rejected rather than shadowing
806 // it. Failures are already logged per-path with a typed code; startup
807 // continues so the rest of the engine stays usable and the operator sees
808 // the whole diagnosis at once.
809 h->server_manager->load_plugins(h->config.mcp);
810}
811
820static std::string build_shared_prompt_prefix(
822 const std::filesystem::path& data_dir) {
823 std::string constitution, app_ctx;
824 entropic::prompts::load_constitution(
826 data_dir, constitution);
827 entropic::prompts::load_app_context(
829 h->config.app_context_disabled, data_dir, app_ctx);
830 std::string prefix;
831 if (!constitution.empty()) { prefix += constitution + "\n\n"; }
832 if (!app_ctx.empty()) { prefix += app_ctx + "\n\n"; }
833 return prefix;
834}
835
859 // gh#95 (v2.7.4): thread the identity `grammar:` key so the orchestrator's
860 // resolve_grammar_key() finds it and constrains the tier's generation.
861 // Without this the field is parsed but dropped here — registers OK, never
862 // enforces (same class as the gh#82/85/94 sampler-threading gaps).
863 if (fm.grammar.has_value()) {
864 tc.grammar = std::filesystem::path(*fm.grammar);
865 }
866 if (fm.temperature.has_value()) { tc.temperature = *fm.temperature; }
867 if (fm.max_output_tokens.has_value()) {
869 }
870 if (fm.top_p.has_value()) { tc.top_p = *fm.top_p; }
871 if (fm.top_k.has_value()) { tc.top_k = *fm.top_k; }
872 if (fm.min_p.has_value()) { tc.min_p = *fm.min_p; }
873 if (fm.presence_penalty.has_value()) {
875 }
876 if (fm.frequency_penalty.has_value()) {
878 }
879 // gh#86 (v2.5.4): repeat_penalty + enable_thinking.
880 if (fm.repeat_penalty.has_value()) { tc.repeat_penalty = *fm.repeat_penalty; }
881 if (fm.enable_thinking.has_value()) {
883 }
884}
885
896 const std::string& name,
898 if (fm.allowed_tools.has_value()) {
899 h->tier_allowed_tools[name] = *fm.allowed_tools;
900 }
901 if (!fm.validation_rules.empty()) {
903 }
904 if (fm.relay_single_delegate) {
905 h->engine->set_relay_single_delegate(name);
906 }
907 // gh#94 (v2.7.3): per-tier frontmatter SAMPLERS are threaded earlier, in
908 // thread_frontmatter_samplers() BEFORE init_orchestrator, so they land in
909 // the orchestrator's by-value config snapshot. Threading them HERE (after
910 // the snapshot) was the gh#94 ordering bug — the values reached h->config
911 // but never the orchestrator's frozen copy, so tiers ran defaults.
912}
913
923 const std::filesystem::path& data_dir) {
924 for (const auto& [name, tier] : h->config.models.tiers) {
925 std::filesystem::path id_path;
926 if (tier.identity.has_value()) {
927 id_path = tier.identity.value();
928 } else if (!tier.identity_disabled) {
929 id_path = data_dir / "prompts" / ("identity_" + name + ".md");
930 }
931 if (id_path.empty() || !std::filesystem::exists(id_path)) {
932 continue;
933 }
935 if (entropic::prompts::load_identity(id_path, id).empty()) {
936 apply_identity_frontmatter(h, name, id.frontmatter);
937 }
938 }
939 // gh#83 (v2.5.2): hand the populated allowlist map to the executor
940 // for dispatch-time enforcement. A pointer to the handle-owned map
941 // keeps this order-independent vs wire_tool_executor.
942 if (h->tool_executor) {
943 h->tool_executor->set_tier_allowed_tools(&h->tier_allowed_tools);
944 }
945}
946
968 const std::filesystem::path& data_dir) {
969 for (auto& [name, tier] : h->config.models.tiers) {
970 std::filesystem::path id_path;
971 if (tier.identity.has_value()) {
972 id_path = tier.identity.value();
973 } else if (!tier.identity_disabled) {
974 id_path = data_dir / "prompts" / ("identity_" + name + ".md");
975 }
976 if (id_path.empty() || !std::filesystem::exists(id_path)) {
977 continue;
978 }
980 if (entropic::prompts::load_identity(id_path, id).empty()) {
981 thread_frontmatter_sampler(tier, id.frontmatter);
982 }
983 }
984}
985
1003static char* tool_history_json_thunk(size_t count, void* ud) {
1004 auto* exec = static_cast<entropic::ToolExecutor*>(ud);
1005 if (exec == nullptr) { return nullptr; }
1006 auto s = exec->tool_history().to_json(count);
1007 if (s.empty() || s == "[]") { return nullptr; }
1008 auto* out = static_cast<char*>(std::malloc(s.size() + 1));
1009 if (out != nullptr) {
1010 std::memcpy(out, s.data(), s.size());
1011 out[s.size()] = '\0';
1012 }
1013 return out;
1014}
1015
1029 h->tool_executor = std::make_unique<entropic::ToolExecutor>(
1030 *h->server_manager,
1031 h->engine->loop_config(),
1032 h->engine->callbacks(),
1033 h->engine->build_directive_hooks());
1036 const std::vector<entropic::ToolCall>& calls,
1037 void* ud) -> std::vector<entropic::Message> {
1038 return static_cast<entropic::ToolExecutor*>(ud)
1039 ->process_tool_calls(ctx, calls);
1040 };
1041 tei.user_data = h->tool_executor.get();
1043 tei.free_fn = [](char* p) { std::free(p); };
1044 h->engine->set_tool_executor(tei);
1045}
1046
1060static char* sp_get_validation(void* ud) {
1061 auto* h = static_cast<entropic_engine*>(ud);
1062 if (h == nullptr || h->validator == nullptr) { return nullptr; }
1063 auto r = h->validator->last_result();
1064 nlohmann::json v;
1065 v["ran"] = true;
1066 switch (r.verdict) {
1068 v["verdict"] = "passed"; break;
1070 v["verdict"] = "revised"; break;
1072 v["verdict"] = "rejected_reverted_length"; break;
1074 v["verdict"] = "rejected_max_revisions"; break;
1076 v["verdict"] = "skipped"; break;
1078 v["verdict"] = "paused_pending_consumer"; break;
1080 v["verdict"] = "passed_consumer_override"; break;
1081 }
1082 v["revisions_applied"] = r.revision_count;
1083 // gh#30 (v2.1.5): structured fields the consumer needs to render
1084 // a "retry / override / re-prompt" UI without parsing free-form
1085 // reason strings.
1086 v["attempt_n"] = r.attempt_n;
1087 nlohmann::json violations = nlohmann::json::array();
1088 for (const auto& vi : r.final_critique.violations) {
1089 violations.push_back({
1090 {"rule", vi.rule},
1091 {"rule_id", vi.rule}, // alias for gh#30 schema
1092 {"rule_text", vi.rule}, // alias for gh#30 schema
1093 {"excerpt", vi.excerpt},
1094 {"quote", vi.excerpt}, // alias matching gh#30 "evidence.quote"
1095 {"explanation", vi.explanation},
1096 {"severity", "error"}, // gh#30: hard rejection only today
1097 });
1098 }
1099 v["violations"] = violations;
1100 return strdup(v.dump().c_str());
1101}
1102
1120 entropic::InferenceInterface& iface,
1121 const std::string& constitution_text) {
1122 entropic::HookInterface hook_iface;
1123 hook_iface.registry = &h->hook_registry;
1124 hook_iface.fire_pre = [](void* reg, entropic_hook_point_t pt,
1125 const char* json, char** out) -> int {
1126 return static_cast<entropic::HookRegistry*>(reg)
1127 ->fire_pre(pt, json, out);
1128 };
1129 hook_iface.fire_post = [](void* reg, entropic_hook_point_t pt,
1130 const char* json, char** out) {
1131 static_cast<entropic::HookRegistry*>(reg)
1132 ->fire_post(pt, json, out);
1133 };
1134 hook_iface.fire_info = [](void* reg, entropic_hook_point_t pt,
1135 const char* json) {
1136 static_cast<entropic::HookRegistry*>(reg)->fire_info(pt, json);
1137 };
1138 h->engine->set_hooks(hook_iface);
1139 // E9 (2.0.6-rc19): forward the same hook dispatch to the tool
1140 // executor so PRE_TOOL_CALL / POST_TOOL_CALL actually fire.
1141 // Prior wiring only touched the engine; tool_executor_ held a
1142 // null HookInterface and silently skipped all tool hooks.
1143 if (h->tool_executor) {
1144 h->tool_executor->set_hooks(hook_iface);
1145 }
1146
1148 && !constitution_text.empty()) {
1149 h->validator = std::make_unique<entropic::ConstitutionalValidator>(
1150 h->config.constitutional_validation, constitution_text);
1151 h->validator->attach(&hook_iface, &iface);
1152 // gh#108 (v2.10.3): resolve reasoning delimiters PER TIER. One
1153 // validator serves every tier, but thinking format is a per-family
1154 // property — before this, the validator hardcoded `<think>` and so
1155 // handed the critique model raw `<|channel>` reasoning on a Gemma-4
1156 // tier, exactly the failure it exists to prevent. The facade is the
1157 // lowest layer that can see both, so it owns the lambda; core takes
1158 // plain strings and keeps no dependency on inference.
1159 h->validator->set_marker_resolver(
1160 [h](const std::string& tier)
1161 -> std::pair<std::string, std::string> {
1162 auto* adapter = (h->orchestrator != nullptr)
1163 ? h->orchestrator->get_adapter(tier) : nullptr;
1164 if (adapter == nullptr) { return {"<think>", "</think>"}; }
1165 auto m = adapter->thinking_markers();
1166 return {m.open, m.close};
1167 });
1168 // E3 (2.0.6-rc17): expose validator verdict via ON_COMPLETE
1169 // hook context.
1170 h->engine->set_validation_provider(sp_get_validation, h);
1171 s_log->info("Constitutional validator attached (max_revisions={})",
1173 }
1174}
1175
1176// ── State provider callbacks ─────────────────────────────
1177
1183static char* sp_get_config(void* ud) {
1184 auto* h = static_cast<entropic_engine*>(ud);
1185 nlohmann::json j;
1186 j["default_tier"] = h->config.models.default_tier;
1187 j["log_level"] = h->config.log_level;
1188 j["log_dir"] = h->config.log_dir.string();
1189 j["ggml_logging"] = h->config.ggml_logging;
1190 return strdup(j.dump().c_str());
1191}
1192
1207 entropic_engine* h, const std::string& tier_name) {
1208 auto data_dir = entropic::config::resolve_data_dir(h->config);
1209 std::string constitution, app_ctx;
1210 entropic::prompts::load_constitution(
1212 data_dir, constitution);
1213 entropic::prompts::load_app_context(
1215 h->config.app_context_disabled, data_dir, app_ctx);
1216 std::string identity_body;
1217 auto it = h->config.models.tiers.find(tier_name);
1218 if (it != h->config.models.tiers.end()) {
1219 identity_body = entropic::prompts::resolve_tier_identity(
1220 it->second, tier_name, data_dir);
1221 }
1222 std::string out;
1223 if (!constitution.empty()) { out += constitution + "\n\n"; }
1224 if (!app_ctx.empty()) { out += app_ctx + "\n\n"; }
1225 if (!identity_body.empty()) { out += identity_body; }
1226
1227 // gh#87 (v2.7.0): tool defs are no longer string-injected into the
1228 // system prompt — they flow via params.tools and common_chat renders
1229 // them in the model's native format. So the assembled system-prompt
1230 // preview no longer includes a tool section.
1231 return out;
1232}
1233
1249static char* sp_get_identities(void* ud) {
1250 auto* h = static_cast<entropic_engine*>(ud);
1251 nlohmann::json arr = nlohmann::json::array();
1252 for (const auto& [name, _] : h->config.models.tiers) {
1253 nlohmann::json entry;
1254 entry["name"] = name;
1255 entry["assembled_prompt"] =
1257 arr.push_back(std::move(entry));
1258 }
1259 return strdup(arr.dump().c_str());
1260}
1261
1267static char* sp_get_tools(void* ud) {
1268 auto* h = static_cast<entropic_engine*>(ud);
1269 if (!h->server_manager) { return strdup("[]"); }
1270 return strdup(h->server_manager->list_tools().c_str());
1271}
1272
1291static char* sp_get_history(int max_entries, void* ud) {
1292 auto* h = static_cast<entropic_engine*>(ud);
1293 if (!h || !h->engine) { return strdup("[]"); }
1294 const auto& msgs = h->engine->get_messages();
1295 nlohmann::json arr = nlohmann::json::array();
1296 int start = 0;
1297 if (max_entries > 0
1298 && static_cast<int>(msgs.size()) > max_entries) {
1299 start = static_cast<int>(msgs.size()) - max_entries;
1300 }
1301 for (int i = start; i < static_cast<int>(msgs.size()); ++i) {
1302 const auto& m = msgs[static_cast<size_t>(i)];
1303 std::string preview = m.content.size() > 200
1304 ? entropic::facade::utf8_safe_substr(m.content, 200) + "..."
1305 : m.content;
1306 arr.push_back({
1307 {"role", m.role},
1308 {"content_preview", preview},
1309 {"token_count_est", m.content.size() / 4u}
1310 });
1311 }
1312 return strdup(arr.dump().c_str());
1313}
1314
1328static char* sp_get_residency(void* ud) {
1329 auto* h = static_cast<entropic_engine*>(ud);
1330 if (!h || !h->orchestrator) {
1331 return strdup("{\"vram_total_bytes\":0,\"vram_budget_bytes\":0,"
1332 "\"vram_headroom_bytes\":0,\"backend\":\"unknown\","
1333 "\"residency\":[]}");
1334 }
1335 return strdup(h->orchestrator->residency_snapshot_json().c_str());
1336}
1337
1343static char* sp_get_state(void* ud) {
1344 auto* h = static_cast<entropic_engine*>(ud);
1345 nlohmann::json j;
1346 j["engine_state"] = h->configured.load() ? "configured" : "init";
1347 j["default_tier"] = h->config.models.default_tier;
1348
1349 nlohmann::json tiers = nlohmann::json::array();
1350 for (const auto& [name, _] : h->config.models.tiers) {
1351 tiers.push_back(name);
1352 }
1353 j["active_tiers"] = tiers;
1354
1355 if (h->server_manager) {
1356 j["working_dir"] = h->server_manager->project_dir().string();
1357 j["registered_servers"] = h->server_manager->server_names();
1358 }
1359 j["data_dir"] = entropic::config::resolve_data_dir(
1360 h->config).string();
1361 j["log_dir"] = h->config.log_dir.string();
1362 return strdup(j.dump().c_str());
1363}
1364
1377static char* sp_get_metrics(void* ud) {
1378 auto* h = static_cast<entropic_engine*>(ud);
1379 if (!h || !h->engine) { return strdup("{}"); }
1380 auto m = h->engine->last_loop_metrics();
1381 nlohmann::json j;
1382 j["iterations"] = m.iterations;
1383 j["tool_calls"] = m.tool_calls;
1384 j["tokens_used"] = m.tokens_used;
1385 j["errors"] = m.errors;
1386 j["duration_ms"] = m.duration_ms();
1387 // Per-tier breakdown (P2-15 follow-up, 2.0.6-rc16.2)
1388 nlohmann::json per_tier = nlohmann::json::object();
1389 for (auto& [tier, tm] : h->engine->per_tier_metrics()) {
1390 per_tier[tier] = {
1391 {"iterations", tm.iterations},
1392 {"tool_calls", tm.tool_calls},
1393 {"tokens_used", tm.tokens_used},
1394 {"errors", tm.errors},
1395 {"duration_ms", tm.duration_ms()},
1396 };
1397 }
1398 j["per_tier"] = per_tier;
1399 return strdup(j.dump().c_str());
1400}
1401
1407static char* sp_get_docs(const char* section, void* ud) {
1408 (void)section;
1409 (void)ud;
1410 return strdup("");
1411}
1412
1423 const char* query, int max_results, void* ud) {
1424 auto* h = static_cast<entropic_engine*>(ud);
1425 if (h == nullptr || !h->storage || query == nullptr) {
1426 return nullptr;
1427 }
1428 std::string out;
1429 if (!h->storage->search_delegations(query, max_results, out)) {
1430 return nullptr;
1431 }
1432 return strdup(out.c_str());
1433}
1434
1445 const char* delegation_id, void* ud) {
1446 auto* h = static_cast<entropic_engine*>(ud);
1447 if (h == nullptr || !h->storage || delegation_id == nullptr) {
1448 return nullptr;
1449 }
1450 std::string out;
1452 delegation_id, out, h->storage.get())) {
1453 return nullptr;
1454 }
1455 return strdup(out.c_str());
1456}
1457
1470 if (!h->server_manager) { return; }
1471 auto* es = dynamic_cast<entropic::EntropicServer*>(
1472 h->server_manager->get_server("entropic"));
1473 if (es == nullptr) { return; }
1474
1477 sp.get_identities = sp_get_identities;
1478 sp.get_tools = sp_get_tools;
1479 sp.get_history = sp_get_history;
1480 sp.get_state = sp_get_state;
1481 sp.get_metrics = sp_get_metrics;
1482 sp.get_docs = sp_get_docs;
1483 // gh#32 (v2.1.6): storage-backed delegation recall + resume.
1484 sp.search_delegations = sp_search_delegations;
1485 sp.load_delegation_conversation = sp_load_delegation_conversation;
1486 // gh#57 (v2.2.4): VRAM residency-set snapshot.
1487 sp.get_residency = sp_get_residency;
1488 sp.user_data = h;
1489 es->set_state_provider(sp);
1490 s_log->info("State provider wired to entropic server");
1491}
1492
1500 if (!h->validator) { return; }
1501 for (const auto& [name, rules] : h->tier_validation_rules) {
1502 h->validator->set_tier_rules(name, rules);
1503 }
1504}
1505
1515 // gh#110 (v2.9.6): was hardcoded true, making the agent loop always
1516 // stream and therefore permanently out of MTP's envelope (MTP
1517 // rejects a bound on_token callback — see mtp_envelope.h).
1521 auto it = h->config.models.tiers.find(h->config.models.default_tier);
1522 if (it != h->config.models.tiers.end()) {
1523 lc.context_length = it->second.context_length;
1524 }
1525 // gh#80 (v2.5.0): map the generation.budget_mode string to the
1526 // BudgetMode enum. Unknown values resolve to off with a warning.
1527 const std::string& bm = h->config.generation.budget_mode;
1528 if (bm == "tokens") {
1530 } else if (bm == "wall_clock") {
1532 } else {
1533 if (bm != "off") {
1534 s_log->warn("Unknown generation.budget_mode '{}' — "
1535 "treating as 'off'", bm);
1536 }
1538 }
1540 // A non-positive limit can't gate anything — disable to avoid a
1541 // pathological "exhausted at zero" loop.
1542 if (lc.budget_limit <= 0) {
1544 }
1545 return lc;
1546}
1547
1560 if (!h->config.mcp.external.enabled) { return; }
1561 auto project_dir = h->config.config_dir.empty()
1562 ? std::filesystem::current_path()
1563 : h->config.config_dir;
1564 h->external_bridge = std::make_unique<entropic::ExternalBridge>(
1565 h, h->config.mcp.external, project_dir);
1566 if (!h->external_bridge->start()) {
1567 s_log->warn("External MCP bridge failed to start");
1568 h->external_bridge.reset();
1569 }
1570}
1571
1598 if (!h->configured.load()) { return ENTROPIC_OK; }
1599 h->last_error = "handle already configured";
1600 s_log->error("{}", h->last_error);
1602}
1603
1623 entropic_handle_t h, const std::filesystem::path& data_dir) {
1624 h->orchestrator = std::make_unique<entropic::ModelOrchestrator>();
1625 if (!h->orchestrator->initialize(h->config)) {
1626 h->last_error = "orchestrator initialization failed";
1627 s_log->error("{}", h->last_error);
1629 }
1630 // Fallback grammar loading: only if initialize() didn't find
1631 // grammars via config_dir. Avoids overwriting patched grammars.
1632 if (h->orchestrator->grammar_registry().size() == 0) {
1633 h->orchestrator->load_grammars_from(data_dir / "grammars");
1634 }
1635 return ENTROPIC_OK;
1636}
1637
1651 entropic_handle_t h, const std::filesystem::path& data_dir) {
1652 h->mcp_auth = std::make_unique<entropic::MCPAuthorizationManager>();
1653 h->identity_manager = std::make_unique<entropic::IdentityManager>(
1655 // P1-7: route identity changes to prompt-cache invalidation.
1656 h->identity_manager->set_cache_invalidator(
1657 [](void* ud) {
1658 auto* orch = static_cast<entropic::ModelOrchestrator*>(ud);
1659 if (orch) { orch->clear_all_prompt_caches(); }
1660 }, h->orchestrator.get());
1661 init_mcp_servers(h, data_dir);
1662
1663 // gh#58 follow-up (v2.2.6): per-handle InterfaceContext. Pre-v2.2.6
1664 // build_orchestrator_interface stored the context in a process-
1665 // global static, so a second configure freed the first handle's
1666 // context and h1.run() segfaulted on a use-after-free.
1670 h->inference_iface.get_tool_prompt = facade_get_tool_prompt;
1671 h->inference_iface.tool_prompt_data = h;
1672 auto lc = build_loop_config(h);
1673 h->engine = std::make_unique<entropic::AgentEngine>(
1674 h->inference_iface, lc, h->config.compaction);
1675 // gh#76 (v2.3.27): wire the compactor registry now that the engine
1676 // owns the CompactionManager that backs the default-compactor
1677 // fallback. Pre-v2.3.27 the field was declared but never
1678 // constructed, so every `entropic_register_compactor` /
1679 // `entropic_compact` call returned INVALID_STATE.
1681 std::make_unique<entropic::CompactorRegistry>(
1682 h->engine->compaction_manager());
1683 rewire_observers(h); // gh#40 + fallout (v2.1.10)
1684 wire_external_interrupt(h); // P1-10
1686}
1687
1699 entropic_handle_t h, const std::filesystem::path& data_dir) {
1700 auto shared_prefix = build_shared_prompt_prefix(h, data_dir);
1701 cache_tier_allowed_tools(h, data_dir); // gh#121: must fill h->tier_allowed_tools
1702 populate_tier_info(h, data_dir, shared_prefix); // before populate reads it
1703 h->engine->set_handoff_rules(h->config.routing.handoff_rules);
1704
1705 wire_hooks_and_validator(h, h->inference_iface, shared_prefix);
1707
1710
1711 h->engine->set_system_prompt(
1712 entropic::prompts::assemble(h->config, data_dir));
1713 if (h->session_logger) {
1714 h->engine->set_session_logger(h->session_logger.get());
1715 }
1716}
1717
1726 if (auto rc = reject_if_configured(h); rc != ENTROPIC_OK) { return rc; }
1727 // gh#59 follow-up (v2.3.7): honor console_logging before any init
1728 // logging fires. When false, strip the stderr console sink so the
1729 // file sink (already installed by setup_session) is the only route
1730 // — TUI consumers paint to fd 2 and can't tolerate engine output
1731 // there. Default (true) is a no-op; operators keep stderr logs.
1732 entropic::log::set_console_enabled(h->config.console_logging);
1733 auto data_dir = entropic::config::resolve_data_dir(h->config);
1734 // gh#94 (v2.7.3): thread per-tier frontmatter samplers into the config
1735 // BEFORE the orchestrator snapshots it by value. The engine-bound
1736 // frontmatter wiring stays in wire_prompts_and_persistence (post-engine).
1737 thread_frontmatter_samplers(h, data_dir);
1738 if (auto rc = init_orchestrator(h, data_dir); rc != ENTROPIC_OK) {
1739 return rc;
1740 }
1741
1742 init_engine_and_interfaces(h, data_dir);
1743 wire_prompts_and_persistence(h, data_dir);
1744
1745 h->configured.store(true);
1747 s_log->info("configure complete");
1748 return ENTROPIC_OK;
1749}
1750
1759 entropic_handle_t handle, const char* config_json) {
1761 auto err = entropic::config::load_config_from_string(
1762 config_json, handle->bundled_models, handle->config);
1763 if (!err.empty()) {
1764 handle->last_error = err;
1765 s_log->error("configure: {}", err);
1767 }
1768 // gh#109 follow-up: parity with configure_dir/configure_from_file.
1769 // Without this, a JSON-string config that sets "log_dir" got no
1770 // session.log at all — config.log_dir was honored for the sqlite/
1771 // conversation store (init_persistence) but never wired to the
1772 // per-handle log dispatcher, so no file sink existed for any
1773 // HandleLogScope-tagged log line to route to.
1774 if (!handle->config.log_dir.empty()) {
1775 entropic::log::setup_session(handle->config.log_dir);
1776 entropic::log::register_handle_log(
1777 handle->log_id, handle->config.log_dir);
1778 }
1779 return configure_common(handle);
1780}
1781
1793 entropic_handle_t handle,
1794 const char* config_json) {
1795 if (!handle || !config_json) {
1796 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
1798 }
1799 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
1800 return c_api_try(handle,
1801 [&]() { return do_configure_json(handle, config_json); });
1802}
1803
1812 entropic_handle_t handle, const char* config_path) {
1814 auto err = entropic::config::load_config_from_file(
1815 config_path, handle->bundled_models, handle->config);
1816 if (!err.empty()) {
1817 handle->last_error = err;
1818 s_log->error("configure_from_file: {}", err);
1820 }
1821 // Parity with configure_dir: if the parsed config specifies a
1822 // log_dir, start session logging there. Without this, consumers
1823 // using the file-based API get no session.log on disk even when
1824 // their YAML declares log_dir.
1825 if (!handle->config.log_dir.empty()) {
1826 entropic::log::setup_session(handle->config.log_dir);
1827 // gh#59 (v2.3.1): per-handle dispatcher file sink so log lines
1828 // emitted within this handle's HandleLogScope route to this
1829 // handle's session.log only.
1830 entropic::log::register_handle_log(
1831 handle->log_id, handle->config.log_dir);
1832 }
1833 return configure_common(handle);
1834}
1835
1847 entropic_handle_t handle,
1848 const char* config_path) {
1849 if (!handle || !config_path) {
1850 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
1852 }
1853 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
1854 return c_api_try(handle,
1855 [&]() { return do_configure_from_file(handle, config_path); });
1856}
1857
1867 entropic_handle_t handle, const char* project_dir) {
1868 // Session logging FIRST — capture everything from preload through init.
1869 if (project_dir && project_dir[0] != '\0') {
1870 entropic::log::setup_session(project_dir);
1871 // gh#59 (v2.3.1): per-handle dispatcher registration. Routes
1872 // session.log writes for this handle's HandleLogScope-tagged
1873 // threads to this handle's file only — no cross-handle bleed.
1874 entropic::log::register_handle_log(handle->log_id, project_dir);
1875 }
1877 std::filesystem::path proj_dir = (project_dir && project_dir[0] != '\0')
1878 ? project_dir : "";
1879 auto err = entropic::config::load_layered(
1880 proj_dir, "default_config.yaml",
1881 handle->bundled_models, handle->config);
1882 if (!err.empty()) {
1883 handle->last_error = err;
1884 s_log->error("configure_dir: {}", err);
1886 }
1887 auto rc = configure_common(handle);
1888 // gh#31 (v2.1.6): propagate the configured project_dir into the
1889 // engine so `AgentEngine::get_repo_dir()` uses it as the sandbox
1890 // snapshot source.
1891 if (rc == ENTROPIC_OK && handle->engine && !proj_dir.empty()) {
1892 handle->engine->set_project_dir(std::filesystem::absolute(proj_dir));
1893 }
1894 return rc;
1895}
1896
1914 entropic_handle_t handle,
1915 const char* project_dir) {
1916 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
1917 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
1918 return c_api_try(handle,
1919 [&]() { return do_configure_dir(handle, project_dir); });
1920}
1921
1935 if (handle == nullptr) {
1936 return;
1937 }
1938 s_log->info("entropic_destroy()");
1939
1940 // Stop external bridge FIRST — it holds a raw pointer to handle
1941 if (handle->external_bridge) {
1942 handle->external_bridge->stop();
1943 handle->external_bridge.reset();
1944 }
1945
1947
1948 // gh#58 follow-up (v2.2.6): release per-handle InterfaceContext
1949 // before the orchestrator unloads, since the context holds a raw
1950 // orchestrator pointer used by the iface callbacks.
1952 handle->inference_iface_ctx = nullptr;
1953
1954 // gh#59 (v2.3.1): release the per-handle session.log file sink so
1955 // a subsequent handle that happens to reuse the same log_id can
1956 // open the file fresh. Safe on never-registered ids.
1957 entropic::log::unregister_handle_log(handle->log_id);
1958
1959 // Phase 1+ subsystem teardown will go here in reverse order.
1960 // Phase 0: struct itself owns hook_registry by value.
1961 delete handle;
1962}
1963
1971const char* entropic_version(void) {
1972 return CONFIG_ENTROPIC_VERSION_STRING;
1973}
1974
1983 return 2;
1984}
1985
1995 if (!handle || !handle->engine) { return 0; }
1996 return handle->engine->seconds_since_last_activity();
1997}
1998
2007void* entropic_alloc(size_t size) {
2008 return malloc(size);
2009}
2010
2018void entropic_free(void* ptr) {
2019 free(ptr);
2020}
2021
2044 entropic_handle_t handle,
2045 const char* input,
2046 char** result_json) {
2047 auto rc = check_orchestrator(handle);
2048 if (rc != ENTROPIC_OK || !input || !result_json || !handle->engine) {
2049 return rc != ENTROPIC_OK ? rc
2050 : (!input || !result_json) ? ENTROPIC_ERROR_INVALID_ARGUMENT
2052 }
2053 // gh#109: log scope only (no api_mutex) — a long turn must not block
2054 // entropic_interrupt() called from another thread.
2055 entropic::log::HandleLogScope log_scope(handle->log_id);
2056 try {
2057 auto result = handle->engine->run_turn(input);
2058 *result_json = alloc_cstr(
2059 facade_json::serialize_messages(result));
2060 // Synthetic completion sentinel — lets observers detect the
2061 // end of a non-streaming run. Contract: (token="", len=0).
2062 // (P0-1, 2.0.6-rc16)
2063 if (handle->stream_observer != nullptr) {
2064 handle->stream_observer(
2065 "", 0, handle->stream_observer_data);
2066 }
2067 return ENTROPIC_OK;
2068 } catch (const std::exception& e) {
2069 handle->last_error = e.what();
2070 s_log->error("run: {}", handle->last_error);
2071 // P3-19 follow-up (2.0.6-rc16.2): surface partial context on
2072 // crash so callers can recover tool_results and any partial
2073 // assistant content accumulated before the failure.
2074 try {
2075 *result_json = alloc_cstr(
2076 facade_json::serialize_messages(
2077 handle->engine->get_messages()));
2078 } catch (...) {
2079 *result_json = nullptr;
2080 }
2082 }
2083}
2084
2094 entropic_handle_t handle, const char* tier, const char* input,
2095 char** result_json) {
2096 try {
2097 auto result = handle->engine->run_turn_as(tier, input);
2098 *result_json = alloc_cstr(facade_json::serialize_messages(result));
2099 // Synthetic completion sentinel (token="", len=0), matching run().
2100 if (handle->stream_observer != nullptr) {
2101 handle->stream_observer("", 0, handle->stream_observer_data);
2102 }
2103 return ENTROPIC_OK;
2104 } catch (const std::exception& e) {
2105 handle->last_error = e.what();
2106 s_log->error("run_as: {}", handle->last_error);
2107 // Define *result_json on the error path, matching entropic_run()'s
2108 // documented identical contract: surface partial context, else nullptr.
2109 try {
2110 *result_json = alloc_cstr(
2111 facade_json::serialize_messages(handle->engine->get_messages()));
2112 } catch (...) {
2113 *result_json = nullptr;
2114 }
2116 }
2117}
2118
2140 entropic_handle_t handle,
2141 const char* tier_or_identity,
2142 const char* input,
2143 char** result_json) {
2144 auto rc = check_orchestrator(handle);
2145 if (rc != ENTROPIC_OK || !tier_or_identity || !input || !result_json
2146 || !handle->engine) {
2147 return rc != ENTROPIC_OK ? rc
2148 : (!tier_or_identity || !input || !result_json)
2151 }
2152 // gh#109: log scope only (no api_mutex) — a long turn must not block
2153 // entropic_interrupt() called from another thread.
2154 entropic::log::HandleLogScope log_scope(handle->log_id);
2155 if (!handle->engine->has_tier(tier_or_identity)) {
2156 handle->last_error =
2157 std::string("unknown tier: ") + tier_or_identity;
2158 s_log->error("run_as: {}", handle->last_error);
2160 }
2161 return run_as_inner(handle, tier_or_identity, input, result_json);
2162}
2163
2171static std::string serialize_batch_results(
2172 const std::vector<entropic::GenerationResult>& results) {
2173 nlohmann::json arr = nlohmann::json::array();
2174 for (const auto& r : results) {
2175 nlohmann::json obj;
2176 obj["content"] = entropic::mcp::sanitize_utf8(r.content);
2177 obj["finish_reason"] = r.finish_reason;
2178 obj["tool_calls"] = nlohmann::json::parse(
2179 entropic::serialize_tool_calls(r.tool_calls), nullptr, false);
2180 arr.push_back(std::move(obj));
2181 }
2182 return arr.dump();
2183}
2184
2199static std::vector<std::vector<entropic::Message>> build_batch_messages(
2200 entropic_handle_t handle, const char** tiers, const char** prompts,
2201 size_t n, std::vector<std::string>& tiers_out) {
2202 std::vector<std::vector<entropic::Message>> msgs(n);
2203 tiers_out.resize(n);
2204 for (size_t i = 0; i < n; ++i) {
2205 tiers_out[i] = (tiers && tiers[i]) ? tiers[i] : "";
2206 const std::string& sys =
2207 handle->engine->tier_system_prompt(tiers_out[i]);
2208 std::vector<entropic::Message> m;
2209 if (!sys.empty()) {
2211 s.role = "system";
2212 s.content = sys;
2213 m.push_back(std::move(s));
2214 }
2216 u.role = "user";
2217 u.content = prompts[i] ? prompts[i] : "";
2218 m.push_back(std::move(u));
2219 msgs[i] = std::move(m);
2220 }
2221 return msgs;
2222}
2223
2247 entropic_handle_t handle,
2248 const char** tiers,
2249 const char** prompts,
2250 size_t n,
2251 char** result_json) {
2252 auto rc = check_orchestrator(handle);
2253 if (rc != ENTROPIC_OK || !prompts || !result_json || !handle->engine
2254 || n == 0) {
2255 return rc != ENTROPIC_OK ? rc
2256 : (!prompts || !result_json || n == 0)
2259 }
2260 // gh#109: log scope only (no api_mutex) — a long turn must not block
2261 // entropic_interrupt() called from another thread.
2262 entropic::log::HandleLogScope log_scope(handle->log_id);
2263 try {
2264 std::vector<std::string> tiers_vec;
2265 auto msgs = build_batch_messages(handle, tiers, prompts, n, tiers_vec);
2266 std::vector<entropic::GenerationParams> params(n);
2267 std::atomic<bool> cancel{false};
2268 auto results = handle->orchestrator->generate_batch(
2269 msgs, params, tiers_vec, cancel);
2270 *result_json = alloc_cstr(serialize_batch_results(results));
2271 return ENTROPIC_OK;
2272 } catch (const std::exception& e) {
2273 handle->last_error = e.what();
2274 s_log->error("run_batch: {}", handle->last_error);
2275 // Define *result_json on the error path (C-ABI: output params must be
2276 // defined). No partial batch recovery — an empty JSON array.
2277 *result_json = alloc_cstr("[]");
2279 }
2280}
2281
2282/* StreamBridge + think filter moved to inference/stream_think_filter.cpp — Step 4 */
2283
2284/* StreamCtx + stream_chunk_cb → engine->run_streaming() — v2.0.2 */
2285
2302 entropic_handle_t handle,
2303 const char* input,
2304 void (*on_token)(const char* token, size_t len, void* user_data),
2305 void* user_data,
2306 int* cancel_flag) {
2307 auto rc = check_orchestrator(handle);
2308 if (rc != ENTROPIC_OK || !input || !on_token || !handle->engine) {
2309 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2310 }
2311
2312 // gh#109: log scope only (no api_mutex) — a long turn must not block
2313 // entropic_interrupt() called from another thread.
2314 entropic::log::HandleLogScope log_scope(handle->log_id);
2315
2316 // Observer multiplexing is handled inside ResponseGenerator — the
2317 // facade passes on_token through untouched. (P0-1, 2.0.6-rc16)
2318 try {
2319 int code = handle->engine->run_streaming(
2320 input, on_token, user_data, cancel_flag);
2321 if (handle->stream_observer != nullptr) {
2322 handle->stream_observer(
2323 "", 0, handle->stream_observer_data);
2324 }
2325 return code == 1 ? ENTROPIC_ERROR_CANCELLED : ENTROPIC_OK;
2326 } catch (const std::exception& e) {
2327 handle->last_error = e.what();
2328 s_log->error("run_streaming: {}", handle->last_error);
2330 }
2331}
2332
2333// ── gh#37 (v2.1.8): multimodal messages entry points ──────────
2334
2352static std::vector<entropic::Message> parse_and_check_vision(
2353 entropic_handle_t handle,
2354 const char* messages_json,
2355 entropic_error_t& out_rc) {
2356 auto msgs = entropic::parse_messages_json(messages_json);
2358 && handle->orchestrator
2359 && !handle->orchestrator->has_vision_capable_tier()) {
2361 return {};
2362 }
2363 out_rc = ENTROPIC_OK;
2364 return msgs;
2365}
2366
2385 entropic_handle_t handle,
2386 const char* messages_json,
2387 char** result_json) {
2389 auto msgs = parse_and_check_vision(handle, messages_json, vrc);
2390 if (vrc != ENTROPIC_OK) { return vrc; }
2391 auto result = handle->engine->run_turn(std::move(msgs));
2392 *result_json = alloc_cstr(
2393 facade_json::serialize_messages(result));
2394 if (handle->stream_observer != nullptr) {
2395 handle->stream_observer(
2396 "", 0, handle->stream_observer_data);
2397 }
2398 return ENTROPIC_OK;
2399}
2400
2421 entropic_handle_t handle,
2422 const char* messages_json,
2423 char** result_json) {
2424 auto rc = check_orchestrator(handle);
2425 if (rc != ENTROPIC_OK
2426 || !messages_json || !result_json || !handle->engine) {
2427 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2428 }
2429 // gh#109: log scope only (no api_mutex) — a long turn must not block
2430 // entropic_interrupt() called from another thread.
2431 entropic::log::HandleLogScope log_scope(handle->log_id);
2432 try {
2433 return run_messages_inner(handle, messages_json, result_json);
2434 } catch (const std::exception& e) {
2435 handle->last_error = e.what();
2436 s_log->error("run_messages: {}", handle->last_error);
2438 }
2439}
2440
2452 entropic_handle_t handle,
2453 const char* messages_json,
2454 void (*on_token)(const char* token, size_t len, void* user_data),
2455 void* user_data,
2456 int* cancel_flag) {
2458 auto msgs = parse_and_check_vision(handle, messages_json, vrc);
2459 if (vrc != ENTROPIC_OK) { return vrc; }
2460 int code = handle->engine->run_streaming(
2461 std::move(msgs), on_token, user_data, cancel_flag);
2462 if (handle->stream_observer != nullptr) {
2463 handle->stream_observer(
2464 "", 0, handle->stream_observer_data);
2465 }
2466 return code == 1 ? ENTROPIC_ERROR_CANCELLED : ENTROPIC_OK;
2467}
2468
2490 entropic_handle_t handle,
2491 const char* messages_json,
2492 void (*on_token)(const char* token, size_t len, void* user_data),
2493 void* user_data,
2494 int* cancel_flag) {
2495 auto rc = check_orchestrator(handle);
2496 if (rc != ENTROPIC_OK
2497 || !messages_json || !on_token || !handle->engine) {
2498 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
2499 }
2500 // gh#109: log scope only (no api_mutex) — a long turn must not block
2501 // entropic_interrupt() called from another thread.
2502 entropic::log::HandleLogScope log_scope(handle->log_id);
2503 try {
2505 handle, messages_json, on_token, user_data, cancel_flag);
2506 } catch (const std::exception& e) {
2507 handle->last_error = e.what();
2508 s_log->error("run_messages_streaming: {}", handle->last_error);
2510 }
2511}
2512
2524 entropic_handle_t handle,
2525 void (*observer)(const char* token, size_t len, void* user_data),
2526 void* user_data) {
2527 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2528 handle->stream_observer = observer;
2529 handle->stream_observer_data = user_data;
2530 // Propagate to engine so every generation path (streaming, batch,
2531 // and child-loop delegations) reaches the observer. (P0-1, 2.0.6-rc16)
2532 if (handle->engine) {
2533 handle->engine->set_stream_observer(observer, user_data);
2534 }
2535 return ENTROPIC_OK;
2536}
2537
2538// ── gh#30 (v2.1.5): validation retry controls ─────────────
2539
2549 entropic_handle_t handle, int enabled) {
2550 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2551 if (handle->validator) {
2552 handle->validator->set_auto_retry(enabled != 0);
2553 }
2554 return ENTROPIC_OK;
2555}
2556
2567 entropic_handle_t handle) {
2568 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2569 if (!handle->validator) { return ENTROPIC_ERROR_INVALID_STATE; }
2570 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2571 return handle->validator->resume_retry();
2572}
2573
2584 entropic_handle_t handle) {
2585 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2586 if (!handle->validator) { return ENTROPIC_ERROR_INVALID_STATE; }
2587 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2588 return handle->validator->accept_last();
2589}
2590
2601 entropic_handle_t handle,
2603 void* user_data) {
2604 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2605 if (handle->validator) {
2606 handle->validator->set_attempt_boundary_cb(cb, user_data);
2607 }
2608 return ENTROPIC_OK;
2609}
2610
2624 entropic_handle_t handle,
2625 ent_delegation_start_cb on_start,
2626 ent_delegation_complete_cb on_complete,
2627 void* user_data) {
2628 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2629 if (handle->engine) {
2630 handle->engine->set_delegation_callbacks(
2631 on_start, on_complete, user_data);
2632 }
2633 return ENTROPIC_OK;
2634}
2635
2655 entropic_handle_t handle,
2656 void (*observer)(int state, void* user_data),
2657 void* user_data) {
2658 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2659 handle->state_observer = observer;
2660 handle->state_observer_data = user_data;
2661 if (handle->engine) {
2662 // gh#40 fallout (v2.1.10): route through the engine's
2663 // persistent state-observer slot rather than the legacy
2664 // EngineCallbacks::on_state_change. The legacy path is
2665 // wiped by run_streaming's set_callbacks() shuffle, so
2666 // wiring there silently failed for streaming runs (the
2667 // exact bridge use case this API was designed for).
2668 handle->engine->set_state_observer(observer, user_data);
2669 }
2670 return ENTROPIC_OK;
2671}
2672
2694 entropic_handle_t handle,
2695 void (*start_cb)(void* user_data),
2696 void (*end_cb)(void* user_data),
2697 void* user_data) {
2698 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2699 handle->critique_start_cb = start_cb;
2700 handle->critique_end_cb = end_cb;
2701 handle->critique_cb_data = user_data;
2702 if (handle->validator) {
2703 handle->validator->set_critique_callbacks(
2704 start_cb, end_cb, user_data);
2705 }
2706 return ENTROPIC_OK;
2707}
2708
2720 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2721 if (!handle->engine) { return ENTROPIC_ERROR_INVALID_STATE; }
2722 handle->engine->interrupt();
2723 return ENTROPIC_OK;
2724}
2725
2726// ── Mid-generation user-message queue (gh#40, v2.1.10) ────────
2727
2746 entropic_handle_t handle, const char* message) {
2748 if (!handle) {
2750 } else if (!message) {
2752 } else if (!handle->engine || !handle->engine->is_running()) {
2754 } else if (!handle->engine->queue_user_message(message)) {
2756 }
2757 return rc;
2758}
2759
2770 entropic_handle_t handle, size_t* count) {
2771 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2772 if (!count) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2773 *count = handle->engine
2774 ? handle->engine->user_message_queue_depth() : 0;
2775 return ENTROPIC_OK;
2776}
2777
2787 entropic_handle_t handle) {
2788 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2789 if (handle->engine) {
2790 handle->engine->clear_user_message_queue();
2791 }
2792 return ENTROPIC_OK;
2793}
2794
2810 entropic_handle_t handle,
2811 void (*observer)(const char*, size_t, void*),
2812 void* user_data) {
2813 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2814 handle->queue_observer = observer;
2815 handle->queue_observer_data = user_data;
2816 if (handle->engine) {
2817 handle->engine->set_queue_observer(observer, user_data);
2818 }
2819 return ENTROPIC_OK;
2820}
2821
2822// ── Conversation Context (v2.0.1) ────────────────────────────
2823
2833 if (!handle || !handle->engine) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2834 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2835 handle->engine->clear_conversation();
2836 return ENTROPIC_OK;
2837}
2838
2850 entropic_handle_t handle, char** messages_json) {
2851 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2852 if (!messages_json) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2853 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2854 *messages_json = alloc_cstr(
2855 facade_json::serialize_messages(handle->engine->get_messages()));
2856 return ENTROPIC_OK;
2857}
2858
2869 entropic_handle_t handle, size_t* count) {
2870 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2871 if (!count) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2872 *count = handle->engine->message_count();
2873 return ENTROPIC_OK;
2874}
2875
2892 entropic_handle_t handle,
2893 size_t* tokens_used,
2894 size_t* capacity) {
2895 if (!handle || !handle->engine) { return ENTROPIC_ERROR_INVALID_HANDLE; }
2896 if (!tokens_used || !capacity) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
2897 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
2898 auto [used, max] = handle->engine->context_usage(
2899 handle->engine->get_messages());
2900 *tokens_used = static_cast<size_t>(used);
2901 *capacity = static_cast<size_t>(max);
2902 return max > 0 ? ENTROPIC_OK : ENTROPIC_ERROR_INVALID_STATE;
2903}
2904
2911 entropic_handle_t handle, const char* tier_name, const char* path) {
2912 if (!handle->orchestrator) { return ENTROPIC_ERROR_INVALID_STATE; }
2913 auto* backend = require_active_backend(handle, tier_name);
2914 std::vector<uint8_t> buf;
2915 if (!backend->save_state(0, buf)) { return ENTROPIC_ERROR_INTERNAL; }
2916 std::ofstream out(path, std::ios::binary | std::ios::trunc);
2917 bool ok = out.is_open()
2918 && out.write(reinterpret_cast<const char*>(buf.data()),
2919 static_cast<std::streamsize>(buf.size())).good();
2920 return ok ? ENTROPIC_OK : ENTROPIC_ERROR_IO;
2921}
2922
2932 entropic_handle_t handle,
2933 const char* tier_name,
2934 const char* path) {
2935 if (!handle || !tier_name || !path) {
2936 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
2938 }
2939 entropic::HandleApiLock lock(handle);
2940 return c_api_try(handle,
2941 [&]() { return do_state_save(handle, tier_name, path); });
2942}
2943
2955static bool read_state_file(const char* path, std::vector<uint8_t>& out_buf) {
2956 std::ifstream in(path, std::ios::binary | std::ios::ate);
2957 if (!in.is_open()) { return false; }
2958 auto sz = static_cast<std::streamsize>(in.tellg());
2959 if (sz <= 0) { return false; }
2960 in.seekg(0, std::ios::beg);
2961 out_buf.resize(static_cast<size_t>(sz));
2962 return static_cast<bool>(
2963 in.read(reinterpret_cast<char*>(out_buf.data()), sz));
2964}
2965
2972 entropic_handle_t handle, const char* tier_name, const char* path) {
2973 if (!handle->orchestrator) { return ENTROPIC_ERROR_INVALID_STATE; }
2974 auto* backend = require_active_backend(handle, tier_name);
2975 std::vector<uint8_t> buf;
2976 if (!read_state_file(path, buf)) { return ENTROPIC_ERROR_IO; }
2977 return backend->restore_state(0, buf)
2979}
2980
2990 entropic_handle_t handle,
2991 const char* tier_name,
2992 const char* path) {
2993 if (!handle || !tier_name || !path) {
2994 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
2996 }
2997 entropic::HandleApiLock lock(handle);
2998 return c_api_try(handle,
2999 [&]() { return do_state_load(handle, tier_name, path); });
3000}
3001
3017 entropic_handle_t handle, char** out) {
3018 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
3019 if (!out) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
3020 *out = sp_get_metrics(handle);
3021 return ENTROPIC_OK;
3022}
3023
3024// ── LoRA Adapter APIs (v1.9.2 → v2.0.0) ────────────────────
3025
3041 entropic_handle_t handle,
3042 const char* adapter_name,
3043 const char* adapter_path,
3044 const char* base_model_path,
3045 float scale)
3046{
3047 auto rc = check_orchestrator(handle);
3048 if (rc != ENTROPIC_OK || !adapter_name || !adapter_path || !base_model_path) {
3049 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3050 }
3051 try {
3052 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3053 auto tier = handle->config.models.find_tier_by_path(base_model_path);
3054 if (tier.empty()) {
3055 throw std::runtime_error("no tier for model: "
3056 + std::string(base_model_path));
3057 }
3058 auto* base = handle->orchestrator->get_backend(tier);
3059 auto* llama = dynamic_cast<entropic::LlamaCppBackend*>(base);
3060 if (!llama || !llama->llama_model_ptr()) {
3061 throw std::runtime_error("backend not ready for tier: " + tier);
3062 }
3063 bool ok = handle->orchestrator->adapter_manager().load(
3064 adapter_name, adapter_path, llama->llama_model_ptr(), scale);
3066 } catch (const std::exception& e) {
3067 handle->last_error = e.what();
3069 }
3070}
3071
3086 entropic_handle_t handle,
3087 const char* adapter_name)
3088{
3089 auto rc = check_orchestrator(handle);
3090 if (rc != ENTROPIC_OK || !adapter_name) {
3091 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3092 }
3093 try {
3094 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3095 auto& mgr = handle->orchestrator->adapter_manager();
3096 auto info = mgr.info(adapter_name);
3097 if (info.state == entropic::AdapterState::COLD) {
3098 throw std::runtime_error("adapter not loaded: "
3099 + std::string(adapter_name));
3100 }
3101 auto tier = handle->orchestrator->last_used_tier();
3102 auto* base = handle->orchestrator->get_backend(tier);
3103 auto* llama = dynamic_cast<entropic::LlamaCppBackend*>(base);
3104 mgr.unload(adapter_name, llama ? llama->llama_context_ptr() : nullptr);
3105 return ENTROPIC_OK;
3106 } catch (const std::exception& e) {
3107 handle->last_error = e.what();
3109 }
3110}
3111
3126 entropic_handle_t handle,
3127 const char* adapter_name)
3128{
3129 auto rc = check_orchestrator(handle);
3130 if (rc != ENTROPIC_OK || !adapter_name) {
3131 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3132 }
3133 try {
3134 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3135 auto tier = handle->orchestrator->last_used_tier();
3136 auto* base = handle->orchestrator->get_backend(tier);
3137 auto* llama = dynamic_cast<entropic::LlamaCppBackend*>(base);
3138 if (!llama || !llama->llama_context_ptr()) {
3139 throw std::runtime_error("no active llama context for swap");
3140 }
3141 bool ok = handle->orchestrator->adapter_manager().swap(
3142 adapter_name, llama->llama_context_ptr());
3144 } catch (const std::exception& e) {
3145 handle->last_error = e.what();
3147 }
3148}
3149
3163 entropic_handle_t handle,
3164 const char* adapter_name)
3165{
3166 if (!handle || !handle->configured.load()
3167 || !handle->orchestrator || !adapter_name) {
3168 return -1;
3169 }
3170 try {
3171 auto st = handle->orchestrator->adapter_manager().state(adapter_name);
3172 return static_cast<int>(st);
3173 } catch (const std::exception& e) {
3174 handle->last_error = e.what();
3175 s_log->error("adapter_state: {}", handle->last_error);
3176 return -1;
3177 }
3178}
3179
3194 entropic_handle_t handle,
3195 const char* adapter_name)
3196{
3197 if (!handle || !handle->configured.load()
3198 || !handle->orchestrator || !adapter_name) {
3199 return nullptr;
3200 }
3201 try {
3202 auto ai = handle->orchestrator->adapter_manager().info(adapter_name);
3203 return alloc_cstr(
3204 facade_json::serialize_adapter_info(ai).c_str());
3205 } catch (const std::exception& e) {
3206 handle->last_error = e.what();
3207 s_log->error("adapter_info: {}", handle->last_error);
3208 return nullptr;
3209 }
3210}
3211
3226{
3227 if (!handle || !handle->configured.load() || !handle->orchestrator) {
3228 return nullptr;
3229 }
3230 try {
3231 auto adapters = handle->orchestrator->adapter_manager().list_adapters();
3232 return alloc_cstr(
3233 facade_json::serialize_adapter_list(adapters).c_str());
3234 } catch (const std::exception& e) {
3235 handle->last_error = e.what();
3236 s_log->error("adapter_list: {}", handle->last_error);
3237 return nullptr;
3238 }
3239}
3240
3241// ── Grammar Registry APIs (v1.9.3 → v2.0.0) ────────────────
3242
3258 entropic_handle_t handle,
3259 const char* key,
3260 const char* gbnf_content)
3261{
3262 auto rc = check_orchestrator(handle);
3263 if (rc != ENTROPIC_OK || !key || !gbnf_content) {
3264 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3265 }
3266 try {
3267 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3268 bool ok = handle->orchestrator->grammar_registry()
3269 .register_grammar(key, gbnf_content);
3270 s_log->info("grammar_register: key={} ok={}", key, ok);
3272 } catch (const std::exception& e) {
3273 handle->last_error = e.what();
3274 s_log->error("grammar_register: {}", handle->last_error);
3276 }
3277}
3278
3293 entropic_handle_t handle,
3294 const char* key,
3295 const char* path)
3296{
3297 auto rc = check_orchestrator(handle);
3298 if (rc != ENTROPIC_OK || !key || !path) {
3299 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3300 }
3301 try {
3302 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3303 bool ok = handle->orchestrator->grammar_registry()
3304 .register_from_file(key, path);
3305 s_log->info("grammar_register_file: key={} ok={}", key, ok);
3306 return ok ? ENTROPIC_OK : ENTROPIC_ERROR_IO;
3307 } catch (const std::exception& e) {
3308 handle->last_error = e.what();
3309 s_log->error("grammar_register_file: {}", handle->last_error);
3311 }
3312}
3313
3328 entropic_handle_t handle,
3329 const char* key)
3330{
3331 auto rc = check_orchestrator(handle);
3332 if (rc != ENTROPIC_OK || !key) {
3333 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3334 }
3335 try {
3336 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3337 bool ok = handle->orchestrator->grammar_registry().deregister(key);
3338 s_log->info("grammar_deregister: key={} ok={}", key, ok);
3340 } catch (const std::exception& e) {
3341 handle->last_error = e.what();
3342 s_log->error("grammar_deregister: {}", handle->last_error);
3344 }
3345}
3346
3361 entropic_handle_t handle,
3362 const char* key)
3363{
3364 if (!handle || !handle->configured.load()
3365 || !handle->orchestrator || !key) {
3366 return nullptr;
3367 }
3368 try {
3369 auto content = handle->orchestrator->grammar_registry().get(key);
3370 return content.empty() ? nullptr : alloc_cstr(content.c_str());
3371 } catch (const std::exception& e) {
3372 handle->last_error = e.what();
3373 s_log->error("grammar_get: {}", handle->last_error);
3374 return nullptr;
3375 }
3376}
3377
3392char* entropic_grammar_validate(const char* gbnf_content) {
3393 if (!gbnf_content) { return alloc_cstr("null input"); }
3394 try {
3395 auto err = entropic::GrammarRegistry::validate(gbnf_content);
3396 return err.empty() ? nullptr : alloc_cstr(err.c_str());
3397 } catch (const std::exception& e) {
3398 return alloc_cstr(e.what());
3399 }
3400}
3401
3417{
3418 if (!handle || !handle->configured.load() || !handle->orchestrator) {
3419 return nullptr;
3420 }
3421 try {
3422 auto entries = handle->orchestrator->grammar_registry().list();
3423 nlohmann::json arr = nlohmann::json::array();
3424 for (const auto& e : entries) {
3425 arr.push_back({{"key", e.key},
3426 {"source", e.source},
3427 {"validated", e.validated},
3428 {"error", e.error}});
3429 }
3430 return alloc_cstr(arr.dump().c_str());
3431 } catch (const std::exception& e) {
3432 handle->last_error = e.what();
3433 s_log->error("grammar_list: {}", handle->last_error);
3434 return nullptr;
3435 }
3436}
3437
3438// ── GPU Resource Profile APIs (v1.9.7 → v2.0.0) ─────────────
3439
3455 entropic_handle_t handle,
3456 const char* profile_json)
3457{
3458 auto rc = check_orchestrator(handle);
3459 if (rc != ENTROPIC_OK || !profile_json) {
3460 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3461 }
3462 try {
3463 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3464 auto j = nlohmann::json::parse(profile_json);
3466 p.name = j.value("name", "");
3467 if (p.name.empty()) { throw std::invalid_argument("missing 'name'"); }
3468 p.n_batch = j.value("n_batch", 512);
3469 p.n_threads = j.value("n_threads", 0);
3470 p.n_threads_batch = j.value("n_threads_batch", 0);
3471 p.description = j.value("description", "");
3472 bool ok = handle->orchestrator->profile_registry()
3473 .register_profile(p);
3474 s_log->info("profile_register: name={} ok={}", p.name, ok);
3476 } catch (const std::exception& e) {
3477 handle->last_error = e.what();
3478 s_log->error("profile_register: {}", handle->last_error);
3480 }
3481}
3482
3497 entropic_handle_t handle,
3498 const char* name)
3499{
3500 auto rc = check_orchestrator(handle);
3501 if (rc != ENTROPIC_OK || !name) {
3502 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3503 }
3504 try {
3505 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
3506 bool ok = handle->orchestrator->profile_registry().deregister(name);
3507 s_log->info("profile_deregister: name={} ok={}", name, ok);
3509 } catch (const std::exception& e) {
3510 handle->last_error = e.what();
3511 s_log->error("profile_deregister: {}", handle->last_error);
3513 }
3514}
3515
3532 entropic_handle_t handle,
3533 const char* name)
3534{
3535 if (!handle || !handle->configured.load()
3536 || !handle->orchestrator || !name) {
3537 return nullptr;
3538 }
3539 try {
3540 auto p = handle->orchestrator->profile_registry().get(name);
3541 nlohmann::json j;
3542 j["name"] = p.name;
3543 j["n_batch"] = p.n_batch;
3544 j["n_threads"] = p.n_threads;
3545 j["n_threads_batch"] = p.n_threads_batch;
3546 j["description"] = p.description;
3547 return alloc_cstr(j.dump().c_str());
3548 } catch (const std::exception& e) {
3549 handle->last_error = e.what();
3550 s_log->error("profile_get: {}", handle->last_error);
3551 return nullptr;
3552 }
3553}
3554
3569{
3570 if (!handle || !handle->configured.load() || !handle->orchestrator) {
3571 return nullptr;
3572 }
3573 try {
3574 auto names = handle->orchestrator->profile_registry().list();
3575 nlohmann::json arr = nlohmann::json(names);
3576 return alloc_cstr(arr.dump().c_str());
3577 } catch (const std::exception& e) {
3578 handle->last_error = e.what();
3579 s_log->error("profile_list: {}", handle->last_error);
3580 return nullptr;
3581 }
3582}
3583
3584// ── Throughput Query APIs (v1.9.7 → v2.0.0) ─────────────────
3585
3601 entropic_handle_t handle,
3602 const char* model_path)
3603{
3604 (void)model_path;
3605 if (!handle || !handle->configured.load() || !handle->orchestrator) {
3606 return 0.0;
3607 }
3608 try {
3609 return handle->orchestrator->throughput_tracker().tok_per_sec();
3610 } catch (const std::exception& e) {
3611 handle->last_error = e.what();
3612 s_log->error("throughput_tok_per_sec: {}", handle->last_error);
3613 return 0.0;
3614 }
3615}
3616
3631 entropic_handle_t handle,
3632 const char* model_path)
3633{
3634 (void)model_path;
3635 if (!handle || !handle->configured.load() || !handle->orchestrator) {
3636 return;
3637 }
3638 try {
3639 handle->orchestrator->throughput_tracker().reset();
3640 s_log->info("throughput_reset: data cleared");
3641 } catch (const std::exception& e) {
3642 handle->last_error = e.what();
3643 s_log->error("throughput_reset: {}", handle->last_error);
3644 }
3645}
3646
3647// ── MCP Authorization APIs (v1.9.4 → v2.0.0) ────────────────
3648
3658 entropic_handle_t handle,
3659 const char* identity_name,
3660 const char* pattern,
3662{
3663 auto rc = check_mcp_auth(handle);
3664 if (rc != ENTROPIC_OK || !identity_name || !pattern) {
3665 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3666 }
3667 auto lvl = static_cast<entropic::MCPAccessLevel>(level);
3668 return handle->mcp_auth->grant(identity_name, pattern, lvl);
3669}
3670
3680 entropic_handle_t handle,
3681 const char* identity_name,
3682 const char* pattern)
3683{
3684 auto rc = check_mcp_auth(handle);
3685 if (rc != ENTROPIC_OK || !identity_name || !pattern) {
3686 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3687 }
3688 return handle->mcp_auth->revoke(identity_name, pattern);
3689}
3690
3700 entropic_handle_t handle,
3701 const char* identity_name,
3702 const char* tool_name,
3704{
3705 if (!handle || !handle->configured.load()
3706 || !handle->mcp_auth || !identity_name || !tool_name) {
3707 return -1;
3708 }
3709 auto lvl = static_cast<entropic::MCPAccessLevel>(level);
3710 return handle->mcp_auth->check_access(identity_name, tool_name, lvl) ? 1 : 0;
3711}
3712
3724 entropic_handle_t handle,
3725 const char* identity_name)
3726{
3727 if (!handle || !handle->configured.load()
3728 || !handle->mcp_auth || !identity_name) {
3729 return nullptr;
3730 }
3731 try {
3732 auto keys = handle->mcp_auth->list_keys(identity_name);
3733 nlohmann::json arr = nlohmann::json::array();
3734 for (const auto& k : keys) {
3735 arr.push_back({{"pattern", k.tool_pattern},
3736 {"level", static_cast<int>(k.level)}});
3737 }
3738 return alloc_cstr(arr.dump().c_str());
3739 } catch (const std::exception& e) {
3740 handle->last_error = e.what();
3741 return nullptr;
3742 }
3743}
3744
3754 entropic_handle_t handle,
3755 const char* granter,
3756 const char* grantee,
3757 const char* pattern,
3759{
3760 auto rc = check_mcp_auth(handle);
3761 if (rc != ENTROPIC_OK || !granter || !grantee || !pattern) {
3762 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3763 }
3764 auto lvl = static_cast<entropic::MCPAccessLevel>(level);
3765 return handle->mcp_auth->grant_from(granter, grantee, pattern, lvl);
3766}
3767
3779{
3780 if (!handle || !handle->configured.load() || !handle->mcp_auth) {
3781 return nullptr;
3782 }
3783 try {
3784 auto json = handle->mcp_auth->serialize_all();
3785 return alloc_cstr(json.c_str());
3786 } catch (const std::exception& e) {
3787 handle->last_error = e.what();
3788 return nullptr;
3789 }
3790}
3791
3801 entropic_handle_t handle,
3802 const char* json)
3803{
3804 auto rc = check_mcp_auth(handle);
3805 if (rc != ENTROPIC_OK || !json) {
3806 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3807 }
3808 bool ok = handle->mcp_auth->deserialize_all(json);
3810}
3811
3812// ── Dynamic Identity Management APIs (v1.9.6 → v2.0.0) ──────
3813
3824 entropic_handle_t handle,
3825 const char* config_json)
3826{
3827 auto rc = check_identity(handle);
3828 if (rc != ENTROPIC_OK || !config_json) {
3829 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3830 }
3831 try {
3832 auto j = nlohmann::json::parse(config_json);
3834 cfg.name = j.value("name", "");
3835 cfg.system_prompt = j.value("system_prompt", "");
3836 if (j.contains("focus") && j["focus"].is_array()) {
3837 cfg.focus = j["focus"].get<std::vector<std::string>>();
3838 }
3840 return handle->identity_manager->create(cfg);
3841 } catch (const std::exception& e) {
3842 handle->last_error = e.what();
3844 }
3845}
3846
3857 entropic_handle_t handle,
3858 const char* name,
3859 const char* config_json)
3860{
3861 auto rc = check_identity(handle);
3862 if (rc != ENTROPIC_OK || !name || !config_json) {
3863 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3864 }
3865 try {
3866 auto j = nlohmann::json::parse(config_json);
3868 cfg.name = name;
3869 cfg.system_prompt = j.value("system_prompt", "");
3870 if (j.contains("focus") && j["focus"].is_array()) {
3871 cfg.focus = j["focus"].get<std::vector<std::string>>();
3872 }
3874 return handle->identity_manager->update(name, cfg);
3875 } catch (const std::exception& e) {
3876 handle->last_error = e.what();
3878 }
3879}
3880
3890 entropic_handle_t handle,
3891 const char* name)
3892{
3893 auto rc = check_identity(handle);
3894 if (rc != ENTROPIC_OK || !name) {
3895 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3896 }
3897 return handle->identity_manager->destroy(name);
3898}
3899
3911 entropic_handle_t handle,
3912 const char* name)
3913{
3914 if (!handle || !handle->identity_manager || !name) { return nullptr; }
3915 try {
3916 auto* cfg = handle->identity_manager->get(name);
3917 if (!cfg) { throw std::runtime_error("identity not found"); }
3918 nlohmann::json j;
3919 j["name"] = cfg->name;
3920 j["system_prompt"] = cfg->system_prompt;
3921 j["origin"] = (cfg->origin == entropic::IdentityOrigin::STATIC)
3922 ? "static" : "dynamic";
3923 return alloc_cstr(j.dump().c_str());
3924 } catch (...) {
3925 return nullptr;
3926 }
3927}
3928
3940{
3941 if (!handle || !handle->identity_manager) { return nullptr; }
3942 try {
3943 auto names = handle->identity_manager->list();
3944 nlohmann::json arr(names);
3945 return alloc_cstr(arr.dump().c_str());
3946 } catch (...) {
3947 return nullptr;
3948 }
3949}
3950
3960 entropic_handle_t handle,
3961 size_t* total,
3962 size_t* dynamic)
3963{
3964 auto rc = check_identity(handle);
3965 if (rc != ENTROPIC_OK || !total) {
3966 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3967 }
3968 *total = handle->identity_manager->count();
3969 if (dynamic) { *dynamic = handle->identity_manager->count_dynamic(); }
3970 return ENTROPIC_OK;
3971}
3972
3973// ── Log-Probability Evaluation APIs (v1.9.10 → v2.0.0) ──────
3974
3990 entropic_handle_t handle,
3991 const char* model_id,
3992 const int32_t* tokens,
3993 int n_tokens,
3995{
3996 auto rc = check_orchestrator(handle);
3997 if (rc != ENTROPIC_OK || !model_id || !tokens || !result || n_tokens < 2) {
3998 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
3999 }
4000 try {
4001 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
4002 auto* backend = require_active_backend(handle, model_id);
4003 auto lr = backend->evaluate_logprobs(tokens, n_tokens);
4004 result->n_tokens = lr.n_tokens;
4005 result->n_logprobs = lr.n_logprobs;
4006 result->perplexity = lr.perplexity;
4007 result->total_logprob = lr.total_logprob;
4008 result->logprobs = static_cast<float*>(
4009 malloc(sizeof(float) * lr.logprobs.size()));
4010 std::copy(lr.logprobs.begin(), lr.logprobs.end(),
4011 result->logprobs);
4012 result->tokens = static_cast<int32_t*>(
4013 malloc(sizeof(int32_t) * lr.tokens.size()));
4014 std::copy(lr.tokens.begin(), lr.tokens.end(),
4015 result->tokens);
4016 return ENTROPIC_OK;
4017 } catch (const std::exception& e) {
4018 handle->last_error = e.what();
4019 s_log->error("get_logprobs: {}", handle->last_error);
4021 }
4022}
4023
4037 entropic_handle_t handle,
4038 const char* model_id,
4039 const int32_t* tokens,
4040 int n_tokens,
4041 float* perplexity)
4042{
4043 auto rc = check_orchestrator(handle);
4044 if (rc != ENTROPIC_OK || !model_id || !tokens || !perplexity || n_tokens < 2) {
4045 return rc != ENTROPIC_OK ? rc : ENTROPIC_ERROR_INVALID_ARGUMENT;
4046 }
4047 try {
4048 entropic::HandleApiLock lock(handle); // gh#59 v2.3.1: mutex + log scope
4049 auto* backend = require_active_backend(handle, model_id);
4050 *perplexity = backend->compute_perplexity(tokens, n_tokens);
4051 return ENTROPIC_OK;
4052 } catch (const std::exception& e) {
4053 handle->last_error = e.what();
4054 s_log->error("compute_perplexity: {}", handle->last_error);
4056 }
4057}
4058
4070{
4071 if (result == nullptr) {
4072 return;
4073 }
4074 free(result->logprobs);
4075 result->logprobs = nullptr;
4076 free(result->tokens);
4077 result->tokens = nullptr;
4078}
4079
4080// ── Vision Query API (v1.9.11 → v2.0.0) ─────────────────────
4081
4096 entropic_handle_t handle,
4097 const char* model_id)
4098{
4099 if (!handle || !handle->configured.load()
4100 || !handle->orchestrator || !model_id) {
4101 return 0;
4102 }
4103 try {
4104 auto* backend = handle->orchestrator->get_backend(model_id);
4105 return (backend && backend->supports(
4107 } catch (const std::exception& e) {
4108 handle->last_error = e.what();
4109 s_log->error("model_has_vision: {}", handle->last_error);
4110 return 0;
4111 }
4112}
4113
4114// ── Constitutional Validation APIs (v1.9.8 → v2.0.0) ────────
4115
4125 entropic_handle_t handle,
4126 bool enabled)
4127{
4128 if (!handle) { return ENTROPIC_ERROR_INVALID_HANDLE; }
4129 if (!handle->validator) { return ENTROPIC_ERROR_INVALID_STATE; }
4130 handle->validator->set_global_enabled(enabled);
4131 return ENTROPIC_OK;
4132}
4133
4143 entropic_handle_t handle,
4144 const char* identity_name,
4145 bool enabled)
4146{
4147 if (!handle || !handle->validator) {
4148 return !handle ? ENTROPIC_ERROR_INVALID_HANDLE
4150 }
4151 if (!identity_name) { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
4152 handle->validator->set_identity_validation(identity_name, enabled);
4153 return ENTROPIC_OK;
4154}
4155
4167{
4168 if (!handle || !handle->validator) { return nullptr; }
4169 try {
4170 auto result = handle->validator->last_result();
4171 nlohmann::json j;
4172 j["content"] = entropic::mcp::sanitize_utf8(result.content);
4173 j["was_revised"] = result.was_revised;
4174 j["revision_count"] = result.revision_count;
4175 return alloc_cstr(j.dump().c_str());
4176 } catch (...) {
4177 return nullptr;
4178 }
4179}
4180
4192 entropic_handle_t handle,
4193 char** prompt_out) {
4194 if (handle == nullptr || prompt_out == nullptr) {
4196 }
4197 (void)handle;
4198 static const char* prompt =
4199 "[SYSTEM DIRECTIVE: SELF-DIAGNOSIS]\n\n"
4200 "Analyze your recent actions and identify any issues. "
4201 "Follow these steps:\n\n"
4202 "1. Call entropic.diagnose to get a full engine state "
4203 "snapshot.\n"
4204 "2. Review the tool call history for:\n"
4205 " - Repeated failures (same tool, same error)\n"
4206 " - Duplicate tool calls (circuit breaker risk)\n"
4207 " - Tool calls that returned errors\n"
4208 " - Unexpected state (wrong phase, wrong tier)\n"
4209 "3. Review your reasoning for:\n"
4210 " - Actions that didn't achieve the stated goal\n"
4211 " - Unnecessary tool calls\n"
4212 " - Missing context that led to errors\n"
4213 "4. Produce a structured assessment:\n"
4214 " - FINDINGS: What went wrong (be specific)\n"
4215 " - ROOT CAUSE: Why it went wrong\n"
4216 " - RECOMMENDATION: What to do differently\n\n"
4217 "Be honest and specific. The goal is accurate "
4218 "self-assessment, not self-defense.\n";
4219 *prompt_out = alloc_cstr(prompt);
4220 return ENTROPIC_OK;
4221}
4222
4246 entropic_handle_t handle,
4247 int* compatible,
4248 char** diagnostic) {
4249 if (handle == nullptr || compatible == nullptr) {
4251 }
4252 if (!handle->orchestrator) {
4254 }
4255 auto info = handle->orchestrator->check_speculative_compat();
4256 *compatible = info.compatible ? 1 : 0;
4257 if (diagnostic != nullptr) {
4258 *diagnostic = info.compatible
4259 ? nullptr
4260 : alloc_cstr(info.diagnostic);
4261 }
4262 return ENTROPIC_OK;
4263}
4264
4265/* ── VRAM-aware tier residency (v2.2.4, gh#57) ─────────── */
4266
4284 entropic_handle_t handle,
4286 void* user_data) {
4287 if (handle == nullptr) { return ENTROPIC_ERROR_INVALID_HANDLE; }
4288 // Engine-not-configured (no orchestrator yet) and observer==nullptr
4289 // both collapse to a no-op set: pre-configure registration is
4290 // ignored (consumers must re-register after configure_*), and
4291 // an explicit nullptr clears any prior slot. Done as one branch
4292 // to stay under the knots return-count gate.
4294 if (observer != nullptr) {
4295 fn = [observer, user_data](
4297 const std::string& tier_name,
4298 const std::string& model_path,
4299 size_t footprint) {
4300 observer(static_cast<entropic_residency_event_t>(event),
4301 tier_name.c_str(),
4302 model_path.c_str(),
4303 footprint,
4304 user_data);
4305 };
4306 }
4307 if (handle->orchestrator) {
4308 handle->orchestrator->set_residency_observer(std::move(fn));
4309 }
4310 return ENTROPIC_OK;
4311}
4312
4330 entropic_handle_t handle,
4331 char** out_json) {
4333 if (handle == nullptr || out_json == nullptr) {
4335 } else if (!handle->orchestrator) {
4337 } else {
4338 std::string snapshot =
4339 handle->orchestrator->residency_snapshot_json();
4340 *out_json = alloc_cstr(snapshot);
4341 if (*out_json == nullptr) {
4343 }
4344 }
4345 return rc;
4346}
4347
4348} // 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:144
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:324
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:711
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:649
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:884
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:222
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.
gh#59 (v2.3.1): RAII guard — sets thread's current handle_id.
Definition logging.h:159
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:184
static void thread_frontmatter_sampler(entropic::TierConfig &tc, const entropic::prompts::IdentityFrontmatter &fm)
Cache per-tier frontmatter fields (allowed_tools, validation_rules, relay).
Definition entropic.cpp:856
static entropic_error_t do_configure_dir(entropic_handle_t handle, const char *project_dir)
entropic_configure_dir body — wrapped by c_api_try.
static void apply_identity_frontmatter(entropic_handle_t h, const std::string &name, const entropic::prompts::IdentityFrontmatter &fm)
Apply a parsed identity's frontmatter to the engine handle.
Definition entropic.cpp:894
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).
static entropic_error_t do_state_load(entropic_handle_t handle, const char *tier_name, const char *path)
Load tier's KV cache from file body (gh#23 v2.3.25).
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:574
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:317
static entropic_error_t do_configure_json(entropic_handle_t handle, const char *config_json)
entropic_configure body — wrapped by c_api_try in the public entry.
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:509
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:820
entropic_error_t entropic_create(entropic_handle_t *handle)
Create a new engine instance.
Definition entropic.cpp:248
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:793
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:529
static void init_persistence(entropic_handle_t h)
Initialize persistence: storage + session logger + StorageInterface.
Definition entropic.cpp:733
void entropic_throughput_reset(entropic_handle_t handle, const char *model_path)
Reset throughput tracking data.
entropic_error_t entropic_state_load(entropic_handle_t handle, const char *tier_name, const char *path)
Restore a tier's KV cache from a file (gh#23 v2.3.25).
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:632
static void rewire_state_observer(entropic_handle_t h)
Propagate any pre-configure state observer to the new engine.
Definition entropic.cpp:469
static entropic_error_t run_as_inner(entropic_handle_t handle, const char *tier, const char *input, char **result_json)
Run + serialize for entropic_run_as (gh#99).
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_run_batch(entropic_handle_t handle, const char **tiers, const char **prompts, size_t n, char **result_json)
Same-prefix batch run on a shared resident model (gh#98).
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.
static std::vector< std::vector< entropic::Message > > build_batch_messages(entropic_handle_t handle, const char **tiers, const char **prompts, size_t n, std::vector< std::string > &tiers_out)
Build per-request messages for entropic_run_batch (gh#98).
static void thread_frontmatter_samplers(entropic_handle_t h, const std::filesystem::path &data_dir)
Thread per-tier frontmatter SAMPLERS into config pre-orchestrator.
Definition entropic.cpp:966
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:489
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.
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:169
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:450
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.
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.
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 std::string serialize_batch_results(const std::vector< entropic::GenerationResult > &results)
Serialize batch results to a JSON array string (gh#98).
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:218
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:431
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.
static entropic_error_t do_configure_from_file(entropic_handle_t handle, const char *config_path)
entropic_configure_from_file body — wrapped by c_api_try.
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.
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:616
static bool read_state_file(const char *path, std::vector< uint8_t > &out_buf)
Load tier's KV cache from file body (gh#23 v2.3.25).
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.
entropic_error_t entropic_state_save(entropic_handle_t handle, const char *tier_name, const char *path)
Save a tier's KV cache to a file (gh#23 v2.3.25, MVP item 13).
entropic_error_t entropic_run_as(entropic_handle_t handle, const char *tier_or_identity, const char *input, char **result_json)
Single-turn blocking run under a named tier (gh#99).
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:770
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:343
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:411
static entropic::StorageInterface build_storage_iface(entropic::SqliteStorageBackend *sb)
Build a populated StorageInterface bound to sb.
Definition entropic.cpp:715
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:921
const char * entropic_last_error(entropic_handle_t handle)
Read the per-handle last_error under api_mutex.
Definition entropic.cpp:155
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:601
const char * entropic_version(void)
Get the library version string.
static entropic_error_t do_state_save(entropic_handle_t handle, const char *tier_name, const char *path)
Save tier's KV cache to file body (gh#23 v2.3.25).
static entropic_error_t check_identity(entropic_handle_t h)
Check handle prerequisites for identity manager APIs.
Definition entropic.cpp:199
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:375
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.
static entropic_error_t c_api_try(entropic_handle_t handle, Fn &&fn)
Run a C-API entry-point body inside an exception barrier.
Definition entropic.cpp:75
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:648
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:106
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:669
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:1152
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:1177
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:821
entropic_residency_event_t
Reasons fired by entropic_residency_observer_t.
Definition entropic.h:796
entropic_mcp_access_level_t
Access level enum for MCP authorization.
Definition entropic.h:1913
void(* ent_validation_attempt_boundary_cb)(int attempt_n, void *user_data)
Stream-side callback fired between constitutional revision passes.
Definition entropic.h:1227
Entropic MCP server — engine-level tools including introspection.
entropic_error_t
Error codes returned by all C API functions.
Definition error.h:37
@ ENTROPIC_OK
Success.
Definition error.h:38
@ ENTROPIC_ERROR_NO_VISION_TIER
Image content present but no vision-capable tier (v2.1.8, gh#41)
Definition error.h:88
@ ENTROPIC_ERROR_ADAPTER_SWAP_FAILED
Swap failed (e.g., base model not ACTIVE) (v1.9.2)
Definition error.h:67
@ ENTROPIC_ERROR_CANCELLED
Operation cancelled via cancel token.
Definition error.h:50
@ ENTROPIC_ERROR_ALREADY_EXISTS
Named resource already exists (v1.9.6)
Definition error.h:73
@ ENTROPIC_ERROR_INTERNAL
Unexpected internal error (bug)
Definition error.h:53
@ ENTROPIC_ERROR_IDENTITY_NOT_FOUND
Identity name not in config (v1.8.9)
Definition error.h:60
@ ENTROPIC_ERROR_GRAMMAR_NOT_FOUND
Grammar key not in registry (v1.9.3)
Definition error.h:69
@ ENTROPIC_ERROR_INVALID_ARGUMENT
NULL pointer, empty string, out-of-range value.
Definition error.h:39
@ ENTROPIC_ERROR_QUEUE_FULL
Mid-gen user-message queue at capacity (v2.1.10, gh#40)
Definition error.h:89
@ ENTROPIC_ERROR_INVALID_HANDLE
NULL or destroyed handle (v1.8.9)
Definition error.h:57
@ ENTROPIC_ERROR_OUT_OF_MEMORY
Allocation failed (system RAM or VRAM)
Definition error.h:51
@ ENTROPIC_ERROR_EVAL_FAILED
Evaluation failed (llama_decode error) (v1.9.10)
Definition error.h:81
@ ENTROPIC_ERROR_ADAPTER_LOAD_FAILED
LoRA file invalid or incompatible with base model (v1.9.2)
Definition error.h:66
@ ENTROPIC_ERROR_PROFILE_NOT_FOUND
Profile name not in registry (v1.9.7)
Definition error.h:75
@ ENTROPIC_ERROR_IO
File/network I/O error.
Definition error.h:52
@ ENTROPIC_ERROR_GENERATE_FAILED
Generation failed (context overflow, model error)
Definition error.h:44
@ ENTROPIC_ERROR_INVALID_CONFIG
Config validation failed (missing fields, bad values)
Definition error.h:40
@ ENTROPIC_ERROR_INVALID_STATE
Operation not valid in current state (e.g., generate before activate)
Definition error.h:41
@ ENTROPIC_ERROR_LOAD_FAILED
Model load failed (corrupt file, OOM, unsupported format)
Definition error.h:43
entropic_hook_point_t
Hook points in the engine lifecycle.
Definition hooks.h:39
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.
@ tokens
Gate on generated tokens since the last tool call.
@ off
Disabled (default) — no thinking-budget gating.
@ wall_clock
Gate on wall-clock seconds since the last tool call.
std::string serialize_tool_calls(const std::vector< ToolCall > &calls)
Serialize parsed tool calls to the C-ABI JSON array form.
@ 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:826
bool enabled
Global enable/disable (default OFF)
Definition config.h:825
bool enabled
Enable external MCP.
Definition config.h:643
Named GPU resource profile for controlling inference hardware knobs.
Definition config.h:296
int n_threads_batch
CPU threads for batch processing (0 = use n_threads)
Definition config.h:300
int n_batch
Batch size for prompt processing (1-2048)
Definition config.h:298
std::string name
Profile name ("maximum", "balanced", "background", "minimal")
Definition config.h:297
int n_threads
CPU threads for generation (0 = auto-detect)
Definition config.h:299
std::string description
Human-readable description.
Definition config.h:301
int budget_limit
gh#80 (v2.5.0) budget ceiling: generated tokens (budget_mode "tokens") or wall-clock seconds (budget_...
Definition config.h:775
std::string budget_mode
gh#80 (v2.5.0) thinking-budget mode: "off" (default), "tokens", or "wall_clock".
Definition config.h:768
bool stream_output
gh#110 (v2.9.6) agent-loop token delivery mode: true streams tokens via the per-token callback path,...
Definition config.h:785
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.
SpeculativeConfig speculative
Speculative decoding (gh#36)
Definition config.h:974
Configuration for the agentic loop.
int budget_limit
Budget ceiling for the active budget_mode: generated tokens (mode tokens) or wall-clock seconds (mode...
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)
bool speculative_enabled
gh#110 (v2.9.6): mirrors inference.speculative.enabled from config, plumbed through since core....
BudgetMode budget_mode
Thinking-budget gating mode (gh#80, v2.5.0).
Mutable state carried through the agentic loop.
ExternalMCPConfig external
External MCP server config (Entropic-as-server)
Definition config.h:686
std::string working_dir
Server working directory (empty = CWD) (v2.0.4)
Definition config.h:688
A message in a conversation.
Definition message.h:36
std::string content
Message text content (always populated)
Definition message.h:38
std::string role
Message role.
Definition message.h:37
std::unordered_map< std::string, TierConfig > tiers
Tier name → config.
Definition config.h:573
std::string find_tier_by_path(const std::filesystem::path &model_path) const
Find tier name by model path.
Definition config.h:588
std::string default_tier
Default tier name.
Definition config.h:575
Full parsed configuration.
Definition config.h:985
PermissionsConfig permissions
Tool permissions.
Definition config.h:989
std::optional< std::filesystem::path > app_context
App context: nullopt = disabled by default.
Definition config.h:1002
std::optional< std::string > app_context_content
Inline app_context text, supplied instead of a path (gh#141).
Definition config.h:1013
CompactionConfig compaction
Auto-compaction settings.
Definition config.h:991
RoutingConfig routing
Routing rules.
Definition config.h:987
InferenceConfig inference
Inference-side knobs (currently speculative decoding only).
Definition config.h:1051
ModelsConfig models
Tiers + router.
Definition config.h:986
ConstitutionalValidationConfig constitutional_validation
Constitutional validation pipeline settings.
Definition config.h:1047
std::filesystem::path log_dir
Session log directory (session.log + session_model.log).
Definition config.h:1023
GenerationConfig generation
Default generation params.
Definition config.h:988
MCPConfig mcp
MCP server settings.
Definition config.h:990
bool console_logging
Emit engine spdlog output to the stderr console sink.
Definition config.h:1044
bool app_context_disabled
true if app_context explicitly disabled
Definition config.h:1003
std::optional< std::filesystem::path > constitution
Constitution: nullopt = bundled default, disabled = explicit false.
Definition config.h:998
bool constitution_disabled
true if constitution explicitly disabled
Definition config.h:999
std::filesystem::path config_dir
Config dir — base for bundled data discovery.
Definition config.h:1019
bool auto_approve
Skip confirmation prompts.
Definition config.h:622
std::unordered_map< std::string, std::vector< std::string > > handoff_rules
Tier handoff rules.
Definition config.h:612
bool enabled
Master switch (off by default)
Definition config.h:933
Storage interface for conversation persistence.
bool(* create_conversation)(const char *title, std::string &conversation_id, void *user_data)
Create a root conversation row.
Tier-specific model configuration.
Definition config.h:442
std::optional< float > frequency_penalty
gh#85
Definition config.h:495
std::optional< float > temperature
Per-tier sampler temperature from identity frontmatter (gh#82).
Definition config.h:479
std::optional< float > top_p
Per-tier sampler knobs from identity frontmatter (gh#85).
Definition config.h:491
std::optional< float > repeat_penalty
Per-tier repeat_penalty + enable_thinking from identity frontmatter (gh#86).
Definition config.h:502
std::optional< float > min_p
gh#85
Definition config.h:493
std::optional< float > presence_penalty
gh#85
Definition config.h:494
std::optional< int > max_output_tokens
Per-tier max output tokens from identity frontmatter (gh#82).
Definition config.h:484
std::optional< int > top_k
gh#85
Definition config.h:492
std::optional< bool > enable_thinking
gh#86
Definition config.h:503
std::optional< std::filesystem::path > grammar
Grammar file path.
Definition config.h:445
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:88
std::optional< std::vector< std::string > > allowed_tools
Tool filter.
Definition manager.h:74
std::optional< float > top_p
Per-tier top_p (gh#85); nullopt = use param default.
Definition manager.h:78
std::optional< float > repeat_penalty
Per-tier repeat_penalty (gh#86); nullopt = use param default.
Definition manager.h:83
std::optional< float > min_p
Per-tier min_p (gh#85); nullopt = use param default.
Definition manager.h:80
std::optional< float > presence_penalty
Per-tier presence_penalty (gh#85); nullopt = use param default.
Definition manager.h:81
std::optional< bool > enable_thinking
Per-tier thinking mode (gh#86); nullopt = use param default.
Definition manager.h:84
std::optional< int > max_output_tokens
Per-tier max output tokens (gh#82); nullopt = use param default.
Definition manager.h:76
std::optional< int > top_k
Per-tier top_k (gh#85); nullopt = use param default.
Definition manager.h:79
bool relay_single_delegate
Skip re-synthesis when single delegate returns (v2.0.11)
Definition manager.h:89
std::optional< float > frequency_penalty
Per-tier frequency_penalty (gh#85); nullopt = use param default.
Definition manager.h:82
std::optional< std::string > grammar
Grammar file reference.
Definition manager.h:72
std::optional< float > temperature
Per-tier sampling temperature (gh#82); nullopt = use param default.
Definition manager.h:77
Parsed identity file: frontmatter + body.
Definition manager.h:111
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::unique_ptr< entropic::CompactorRegistry > compactor_registry
Compaction strategies.
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:2419
float perplexity
exp(-mean(logprobs)) over the sequence.
Definition entropic.h:2422
int32_t * tokens
Input tokens echoed back (N values).
Definition entropic.h:2421
int n_logprobs
Number of logprob values (n_tokens - 1).
Definition entropic.h:2425
int n_tokens
Number of input tokens.
Definition entropic.h:2424
float * logprobs
Per-token log-probabilities (N-1 values).
Definition entropic.h:2420
float total_logprob
Sum of all logprob values.
Definition entropic.h:2423
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.