Entropic 2.14.3
Local-first agentic inference engine
Loading...
Searching...
No Matches
entropic_mcp.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
12#include "engine_handle.h"
13
14#include <entropic/entropic.h>
19
20#include "json_serializers.h"
21
22#include <cstring>
23#include <filesystem>
24#include <fstream>
25#include <iterator>
26#include <optional>
27#include <memory>
28#include <string>
29#include <vector>
30
31static auto logger = entropic::log::get("facade.mcp");
32
45
46// ── Named workspaces (gh#166, v2.13.0) ──────────────────────
47
82 auto& fs_cfg = mcp.filesystem;
83 if (fs_cfg.allow_outside_root != entropic::OutsideRootAccess::refuse
84 || !fs_cfg.outside_root_allow.empty()) {
85 logger->info("workspace servers confine to their root (host "
86 "mcp.filesystem.allow_outside_root, outside_root_allow "
87 "({} entries) and the path approver apply to the "
88 "default set only)", fs_cfg.outside_root_allow.size());
89 }
90 fs_cfg.allow_outside_root = entropic::OutsideRootAccess::refuse;
91 fs_cfg.outside_root_allow.clear();
92 return mcp;
93}
94
110 const entropic::OutsideRootRequest& req, void* ud) {
111 auto* h = static_cast<entropic_handle_t>(ud);
112 ent_path_approval_cb cb = nullptr;
113 void* cb_data = nullptr;
114 {
115 std::lock_guard<std::mutex> lock(h->path_approval_mutex);
116 cb = h->path_approval_cb;
117 cb_data = h->path_approval_data;
118 }
120 if (cb != nullptr) {
121 const std::string session = entropic::current_run_session();
123 req.path.c_str(), req.root.c_str(), req.tool.c_str(),
125 ? ENT_PATH_ACCESS_WRITE : ENT_PATH_ACCESS_READ,
126 session.c_str()};
127 verdict = cb(&c_req, cb_data) == ENT_DECISION_ACCEPT
130 }
131 return verdict;
132}
133
141 if (h != nullptr && h->server_manager) {
142 h->server_manager->set_outside_root_approver(outside_root_thunk, h);
143 }
144}
145
158static bool discovered_server_thunk(const std::string& context_json, void* ud) {
159 auto* h = static_cast<entropic_handle_t>(ud);
160 constexpr auto kPoint = ENTROPIC_HOOK_PRE_MCP_SERVER_CONNECT;
161 bool approved = false;
162 if (h->hook_registry.hook_count(kPoint) > 0) {
163 char* ignored = nullptr;
164 const int rc = h->hook_registry.fire_pre(kPoint, context_json.c_str(),
165 &ignored);
166 free(ignored);
167 approved = rc == 0;
168 }
169 return approved;
170}
171
180 return h->config.config_dir.empty() ? std::filesystem::current_path()
181 : h->config.config_dir;
182}
183
192 const auto& ext = h->config.mcp.external;
193 std::filesystem::path sock;
194 if (ext.enabled) {
195 sock = ext.socket_path.has_value()
196 ? *ext.socket_path : compute_socket_path(bridge_project_dir(h));
197 }
198 return sock;
199}
200
210 entropic_handle_t h, const std::string& workspace,
211 const std::optional<std::string>& app_context) {
212 if (!app_context) {
213 h->engine->clear_workspace_prompts(workspace);
214 return;
215 }
216 const auto data_dir = entropic::config::resolve_data_dir(h->config);
217 std::string constitution, handle_ctx;
218 entropic::prompts::load_shared_prompt_sources(h->config, data_dir,
219 constitution, handle_ctx);
220 std::string prefix;
221 if (!constitution.empty()) { prefix += constitution + "\n\n"; }
222 if (!app_context->empty()) { prefix += *app_context + "\n\n"; }
224 prompts.default_prompt = prefix;
225 for (const auto& [name, tier] : h->config.models.tiers) {
226 prompts.tier_prompts[name] = prefix
227 + entropic::prompts::resolve_tier_identity_full(tier, name, data_dir).body;
228 }
229 auto def = prompts.tier_prompts.find(h->config.models.default_tier);
230 if (def != prompts.tier_prompts.end()) { prompts.default_prompt = def->second; }
231 h->engine->set_workspace_prompts(workspace, std::move(prompts));
232}
233
243 ServerManager& servers,
244 bool with_declared) {
245 MCPConfig policy = h->config.mcp;
246 if (!with_declared) { policy.external_servers.clear(); }
247 servers.set_mcp_config(policy);
250}
251
284 const std::string& session_key,
285 const std::filesystem::path& path,
286 bool entering, void* ud) {
287 auto* h = static_cast<entropic_handle_t>(ud);
288 auto* servers = entropic::workspace_servers(h, session_key);
289 if (servers == nullptr) { return; }
290 logger->info("delegation dir swap: session='{}' -> {} ({})",
291 session_key, path.string(),
292 entering ? "enter" : "restore");
293 if (entering) {
294 servers->enter_working_dir(path);
295 } else {
296 servers->leave_working_dir(path);
297 }
298}
299
316 const std::string& key) {
317 std::size_t released = 0;
318 if (h == nullptr) { return released; }
319 if (h->server_manager) {
320 released += h->server_manager->release_session(key);
321 }
322 std::lock_guard<std::mutex> lock(h->workspace_mutex);
323 for (const auto& [name, ws] : h->workspaces) {
324 if (ws && ws->servers) {
325 released += ws->servers->release_session(key);
326 }
327 }
328 return released;
329}
330
347extern "C" ENTROPIC_EXPORT entropic_error_t
350 void* user_data) {
351 if (handle == nullptr) { return ENTROPIC_ERROR_INVALID_HANDLE; }
353 {
354 std::lock_guard<std::mutex> lock(handle->path_approval_mutex);
355 handle->path_approval_cb = cb;
356 handle->path_approval_data = user_data;
357 }
358 logger->info("path approval callback {}",
359 cb != nullptr ? "registered" : "cleared");
360 return ENTROPIC_OK;
361}
362
380static std::unique_ptr<EntropicWorkspace> build_workspace(
381 entropic_handle_t h, const std::string& name,
382 const std::filesystem::path& dir) {
383 auto ws = std::make_unique<EntropicWorkspace>();
384 ws->name = name;
385 ws->root = dir;
386 ws->servers = std::make_unique<entropic::ServerManager>(
387 h->config.permissions, dir);
388 std::vector<std::string> tier_names;
389 std::vector<std::string> require_context;
390 for (const auto& [tier, cfg] : h->config.models.tiers) {
391 if (tier != h->config.models.default_tier) {
392 tier_names.push_back(tier);
393 }
394 if (cfg.requires_context) { require_context.push_back(tier); }
395 }
396 auto data_dir = entropic::config::resolve_data_dir(h->config);
397 const auto mcp = confined_mcp_config(h);
398 ws->servers->init_builtins(mcp, tier_names,
399 data_dir.string(), require_context);
400 ws->servers->load_plugins(mcp);
401 // gh#199: the workspace inherits the handle's enablement policy, so its
402 // root's .mcp.json launches nothing the operator did not enable.
403 // v2.14.1: and it launches the operator's DECLARED servers itself,
404 // rooted here — a bound session dispatches only to this set, so a
405 // declared server left on the base set was invisible to it (decision
406 // #87, revising #83).
407 entropic::wire_external_server_policy(h, *ws->servers, true);
408 return ws;
409}
410
427 entropic_handle_t handle, const char* name, const char* dir,
428 std::filesystem::path& root) {
429 if (name == nullptr || *name == '\0' || dir == nullptr
430 || *dir == '\0') {
432 }
433 std::error_code ec;
434 root = std::filesystem::absolute(dir, ec);
435 std::string err;
436 if (ec || !std::filesystem::is_directory(root, ec)) {
437 err = std::string("workspace dir is not a directory: ") + dir;
438 } else {
439 std::lock_guard<std::mutex> guard(handle->workspace_mutex);
440 if (handle->workspaces.count(name) > 0) {
441 err = std::string("workspace already exists: ") + name;
442 }
443 }
444 if (!err.empty()) {
445 handle->last_error = err;
447 }
448 return ENTROPIC_OK;
449}
450
470 entropic_handle_t handle, const std::filesystem::path& root,
471 std::optional<std::string>& text) {
472 namespace fs = std::filesystem;
473 const auto& rel = handle->config.workspace_app_context;
474 std::error_code ec;
475 if (rel.empty() || !fs::exists(root / rel, ec)) { return ENTROPIC_OK; }
476 const auto file = fs::weakly_canonical(root / rel, ec);
477 const auto base = fs::weakly_canonical(root, ec);
478 std::ifstream in(file, std::ios::binary);
480 if (file.lexically_relative(base).string().rfind("..", 0) == 0) {
481 handle->last_error = "workspace_app_context " + (root / rel).string()
482 + " resolves outside the workspace root (" + file.string() + ")";
484 } else if (!in) {
485 handle->last_error = "cannot read workspace_app_context " + file.string();
487 } else {
488 text = std::string(std::istreambuf_iterator<char>(in), {});
489 logger->info("workspace app_context loaded from {} ({} bytes)",
490 file.string(), text->size());
491 }
492 if (rc != ENTROPIC_OK) { logger->error("{}", handle->last_error); }
493 return rc;
494}
495
507extern "C" ENTROPIC_EXPORT entropic_error_t
509 const char* name,
510 const char* dir) {
511 auto rc = check_server_mgr(handle);
512 if (rc != ENTROPIC_OK) { return rc; }
513 entropic::HandleApiLock lock(handle);
514 std::filesystem::path root;
515 std::optional<std::string> ctx;
516 rc = validate_workspace_args(handle, name, dir, root);
517 if (rc == ENTROPIC_OK) { rc = load_workspace_context_file(handle, root, ctx); }
518 if (rc == ENTROPIC_OK) {
519 try {
520 auto ws = build_workspace(handle, name, root);
521 ws->app_context = ctx;
522 ws->servers->initialize();
523 std::lock_guard<std::mutex> guard(handle->workspace_mutex);
524 handle->workspaces[name] = std::move(ws);
525 entropic::apply_workspace_app_context(handle, name, ctx); // gh#179
526 logger->info("workspace '{}' created at {} (app_context: {})",
527 name, root.string(), ctx ? "its own" : "the handle's");
528 } catch (const std::exception& e) {
529 // Design rule #5: exceptions never cross the .so boundary.
530 handle->last_error = e.what();
532 }
533 }
534 return rc;
535}
536
548extern "C" ENTROPIC_EXPORT entropic_error_t
550 const char* session_key,
551 const char* name) {
552 auto rc = check_server_mgr(handle);
553 if (rc != ENTROPIC_OK) { return rc; }
554 if (name == nullptr || *name == '\0' || !handle->engine) {
556 }
557 entropic::HandleApiLock lock(handle);
558 std::string key = session_key != nullptr ? session_key : "";
559 // Single-exit accumulator (knots returns gate ≤ 3). A conversation
560 // cites paths relative to the root it was built in, so re-rooting one
561 // mid-life invalidates every citation in it with no error anywhere:
562 // refuse rather than silently re-point.
563 rc = ENTROPIC_OK;
564 if (handle->engine->message_count_for(key) > 0) {
565 handle->last_error =
566 "session '" + key + "' already holds messages; bind a "
567 "workspace before its first turn";
569 } else {
570 std::lock_guard<std::mutex> guard(handle->workspace_mutex);
571 if (handle->workspaces.count(name) == 0) {
572 handle->last_error = std::string("unknown workspace: ") + name;
574 } else {
575 handle->session_workspace[key] = name;
576 handle->engine->bind_session_workspace(key, name); // gh#179
577 logger->info("session '{}' bound to workspace '{}'", key, name);
578 }
579 }
580 return rc;
581}
582
594extern "C" ENTROPIC_EXPORT entropic_error_t
596 const char* name,
597 const char* app_context) {
598 auto rc = check_server_mgr(handle);
599 if (rc != ENTROPIC_OK) { return rc; }
600 if (name == nullptr || *name == '\0') { return ENTROPIC_ERROR_INVALID_ARGUMENT; }
601 entropic::HandleApiLock lock(handle);
602 std::optional<std::string> ctx;
603 if (app_context != nullptr) { ctx = app_context; }
604 {
605 std::lock_guard<std::mutex> guard(handle->workspace_mutex);
606 auto it = handle->workspaces.find(name);
607 if (it == handle->workspaces.end()) {
608 handle->last_error = std::string("unknown workspace: ") + name;
610 } else {
611 it->second->app_context = ctx;
612 }
613 }
614 try {
615 if (rc == ENTROPIC_OK) {
616 entropic::apply_workspace_app_context(handle, name, ctx);
617 logger->info("workspace '{}' app_context {}", name,
618 ctx ? "set (" + std::to_string(ctx->size()) + " bytes)"
619 : std::string("reset to the handle's"));
620 }
621 } catch (const std::exception& e) {
622 // Design rule #5: exceptions never cross the .so boundary.
623 handle->last_error = e.what();
625 }
626 return rc;
627}
628
666 const char* name, const nlohmann::json& j) {
668 spec.name = name;
669 spec.command = j.value("command", "");
670 spec.url = j.value("url", "");
671 spec.transport = j.value("transport",
672 spec.url.empty() ? "stdio" : "sse");
673 if (j.contains("args") && j["args"].is_array()) {
674 spec.args = j["args"].get<std::vector<std::string>>();
675 }
676 if (j.contains("env") && j["env"].is_object()) {
677 for (auto& [key, val] : j["env"].items()) {
679 logger->warn("register_mcp_server '{}': blocked env var "
680 "'{}' — skipping", name, key);
681 continue;
682 }
683 if (val.is_string()) {
684 spec.env[key] = val.get<std::string>();
685 }
686 }
687 }
688 return spec;
689}
690
708 entropic_handle_t handle, const std::string& ws_name,
710 if (ws_name.empty()) { return handle->server_manager.get(); }
711 std::lock_guard<std::mutex> guard(handle->workspace_mutex);
712 auto it = handle->workspaces.find(ws_name);
713 if (it == handle->workspaces.end()) { return nullptr; }
714 spec.working_dir = it->second->root.string();
715 return it->second->servers.get();
716}
717
729extern "C" ENTROPIC_EXPORT entropic_error_t
731 entropic_handle_t handle,
732 const char* name,
733 const char* config_json) {
734
735 auto rc = check_server_mgr(handle);
736 if (rc != ENTROPIC_OK || !name || !config_json) {
738 }
739
740 try {
741 auto j = nlohmann::json::parse(config_json);
742 auto spec = parse_external_server_spec(name, j);
743 // gh#166 (v2.13.0): an optional "workspace" field registers the
744 // server on that workspace's set instead of the handle's, and
745 // spawns it with cwd = the workspace root — which is what a
746 // repo-scoped server like `clew-mcp --repo` actually needs.
747 auto ws_name = j.value("workspace", std::string{});
748 auto* servers = resolve_registration_target(handle, ws_name, spec);
749 if (servers == nullptr) {
750 handle->last_error = "unknown workspace: " + ws_name;
752 } else {
753 servers->connect_external_server(spec);
754 logger->info("register_mcp_server: name='{}' env_keys={} "
755 "workspace='{}'", name, spec.env.size(), ws_name);
756 }
757 } catch (const std::exception& e) {
758 handle->last_error = e.what();
760 }
761 return rc;
762}
763
775extern "C" ENTROPIC_EXPORT entropic_error_t
777 entropic_handle_t handle,
778 const char* name) {
779
780 auto rc = check_server_mgr(handle);
781 if (rc != ENTROPIC_OK || !name) {
783 }
784
785 try {
786 handle->server_manager->disconnect_external_server(name);
787 logger->info("deregister_mcp_server: name='{}'", name);
788 return ENTROPIC_OK;
789 } catch (const std::exception& e) {
790 handle->last_error = e.what();
792 }
793}
794
806extern "C" ENTROPIC_EXPORT char*
808 if (!handle || !handle->configured.load()) {
809 return nullptr;
810 }
811 try {
812 nlohmann::json arr = nlohmann::json::array();
813 if (handle->server_manager) {
814 auto servers = handle->server_manager->list_server_info();
815 for (const auto& [name, s] : servers) {
816 arr.push_back({{"name", s.name},
817 {"transport", s.transport},
818 {"status", s.status},
819 {"source", s.source}});
820 }
821 }
822 return strdup(arr.dump().c_str());
823 } catch (const std::exception& e) {
824 handle->last_error = e.what();
825 return nullptr;
826 }
827}
gh#59 (v2.3.1): RAII guard combining api_mutex + log scope.
Manages MCP server instances and routes tool calls.
void set_discovered_server_approver(DiscoveredServerApprover fn, void *user_data)
Install the discovered-server approver (v2.14.0, gh#199).
void set_mcp_config(const MCPConfig &config)
Set MCP config for external server initialization.
void set_own_bridge_socket(const std::filesystem::path &socket)
The socket this handle's external bridge binds (v2.14.0, gh#200).
gh#59 (v2.3.1): RAII guard — sets thread's current handle_id.
Definition logging.h:159
Private definition of the entropic_engine struct.
Public C API for the Entropic inference engine.
ent_decision_t(* ent_path_approval_cb)(const ent_path_approval_request_t *req, void *user_data)
Host decision for one outside-root filesystem access.
Definition entropic.h:1807
ENTROPIC_EXPORT entropic_error_t entropic_workspace_create(entropic_handle_t handle, const char *name, const char *dir)
Create a named workspace (gh#166) — see entropic.h.
ENTROPIC_EXPORT entropic_error_t entropic_register_mcp_server(entropic_handle_t handle, const char *name, const char *config_json)
Register an external MCP server from JSON config (C ABI).
static entropic::ExternalServerConfig parse_external_server_spec(const char *name, const nlohmann::json &j)
Register an external MCP server at runtime.
static entropic_error_t check_server_mgr(entropic_handle_t h)
Check handle prerequisites for MCP server APIs.
static entropic_error_t validate_workspace_args(entropic_handle_t handle, const char *name, const char *dir, std::filesystem::path &root)
Validate workspace-create arguments and resolve the root.
static entropic::ServerManager * resolve_registration_target(entropic_handle_t handle, const std::string &ws_name, entropic::ExternalServerConfig &spec)
Servers a registration targets, and the cwd it spawns in (gh#166).
ENTROPIC_EXPORT entropic_error_t entropic_workspace_set_app_context(entropic_handle_t handle, const char *name, const char *app_context)
Give a workspace its own app_context (gh#179) — see entropic.h .
static std::unique_ptr< EntropicWorkspace > build_workspace(entropic_handle_t h, const std::string &name, const std::filesystem::path &dir)
Build a workspace's own server instances rooted at dir.
static entropic::MCPConfig confined_mcp_config(entropic_handle_t h)
The handle's MCP config, confined to a workspace root (gh#166).
ENTROPIC_EXPORT entropic_error_t entropic_session_bind_workspace(entropic_handle_t handle, const char *session_key, const char *name)
Bind a session to a workspace (gh#166) — see entropic.h.
ENTROPIC_EXPORT entropic_error_t entropic_set_path_approval_callback(entropic_handle_t handle, ent_path_approval_cb cb, void *user_data)
Register the outside-root path approver — see entropic.h.
ENTROPIC_EXPORT char * entropic_list_mcp_servers(entropic_handle_t handle)
List all MCP servers as JSON array.
static entropic::OutsideRootVerdict outside_root_thunk(const entropic::OutsideRootRequest &req, void *ud)
FilesystemServer → consumer bridge for outside-root approval.
static bool discovered_server_thunk(const std::string &context_json, void *ud)
Ask the handle's PRE_MCP_SERVER_CONNECT hooks about one server.
static entropic_error_t load_workspace_context_file(entropic_handle_t handle, const std::filesystem::path &root, std::optional< std::string > &text)
Read a new workspace's opt-in app_context file (gh#179).
ENTROPIC_EXPORT entropic_error_t entropic_deregister_mcp_server(entropic_handle_t handle, const char *name)
Deregister an external MCP server.
entropic_error_t
Error codes returned by all C API functions.
Definition error.h:37
@ ENTROPIC_OK
Success.
Definition error.h:38
@ ENTROPIC_ERROR_INTERNAL
Unexpected internal error (bug)
Definition error.h:53
@ ENTROPIC_ERROR_SERVER_NOT_FOUND
MCP server name not found (v1.8.7)
Definition error.h:55
@ ENTROPIC_ERROR_CONNECTION_FAILED
Transport connection failed (v1.8.7)
Definition error.h:56
@ ENTROPIC_ERROR_INVALID_ARGUMENT
NULL pointer, empty string, out-of-range value.
Definition error.h:39
@ ENTROPIC_ERROR_INVALID_HANDLE
NULL or destroyed handle (v1.8.9)
Definition error.h:57
@ ENTROPIC_ERROR_IO
File/network I/O error.
Definition error.h:52
@ ENTROPIC_ERROR_INVALID_STATE
Operation not valid in current state (e.g., generate before activate)
Definition error.h:41
std::string err
size_exceeded, when set
@ ENTROPIC_HOOK_PRE_MCP_SERVER_CONNECT
A discovered .mcp.json server is about to be launched (v2.14.0).
Definition hooks.h:120
JSON serialization helpers for the facade.
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.
Discovers external MCP servers from .mcp.json files.
std::filesystem::path compute_socket_path(const std::filesystem::path &project_dir)
Compute project-unique Unix socket path for self-detection.
void wire_external_server_policy(entropic_handle_t h, ServerManager &servers, bool with_declared)
Give a server set the handle's external-server policy (v2.14.0).
OutsideRootVerdict
The host's answer to an OutsideRootRequest (v2.13.0).
Definition filesystem.h:120
@ approved
Serve this one call.
@ rejected
The approver said no.
@ no_approver
No approver is registered — refuse, fail loud.
void apply_workspace_app_context(entropic_handle_t h, const std::string &workspace, const std::optional< std::string > &app_context)
Give a workspace's sessions prompts built on its own app_context (gh#179), or return them to the hand...
@ refuse
false: every escape refused ("Path escapes project root")
bool is_blocked_env_var(const std::string &key)
Environment variable blocklist for .mcp.json env field.
std::filesystem::path bridge_project_dir(entropic_handle_t h)
The project dir the external bridge derives its socket from.
ENTROPIC_EXPORT std::string current_run_session()
The session key of the run executing on this thread (gh#166).
Definition run_scope.cpp:95
@ write
write_file, edit_file
ENTROPIC_EXPORT void swap_session_tool_dir(const std::string &session_key, const std::filesystem::path &path, bool entering, void *ud)
The facade's gh#160 ScopedSandbox swap callback.
std::filesystem::path expected_bridge_socket(entropic_handle_t h)
The socket this handle's external bridge binds, or empty when the handle runs no bridge (v2....
void wire_outside_root_approver(entropic_handle_t h)
Point the DEFAULT server set's filesystem server at the handle's path-approval slot (v2....
std::size_t release_session_tool_state(entropic_handle_t h, const std::string &key)
Release a session's per-session tool state on every server set (gh#158).
entropic::ServerManager * workspace_servers(entropic_handle_t h, const std::string &key)
The MCP servers a session's tools must run against (gh#166).
One filesystem access OUTSIDE the project root, awaiting the host's decision.
Definition entropic.h:1783
std::optional< std::filesystem::path > socket_path
Socket path (nullopt = derived)
Definition config.h:858
Parsed server entry from .mcp.json.
std::map< std::string, std::string > env
Stdio env vars.
std::string command
Stdio command (empty for SSE)
std::string working_dir
gh#166 (v2.13.0): cwd for the spawned child ("" = inherit).
std::string transport
"stdio" | "sse"
std::vector< std::string > args
Stdio command args.
std::string url
SSE URL (empty for stdio)
MCP server configuration.
Definition config.h:953
FilesystemConfig filesystem
Filesystem server config.
Definition config.h:960
std::unordered_map< std::string, ExternalServerEntry > external_servers
Named external servers the operator declared (v1.8.7).
Definition config.h:1001
ExternalMCPConfig external
External MCP server config (Entropic-as-server)
Definition config.h:962
std::unordered_map< std::string, TierConfig > tiers
Tier name → config.
Definition config.h:690
std::string default_tier
Default tier name.
Definition config.h:692
One outside-root access awaiting the host's decision (v2.13.0).
Definition filesystem.h:104
PathAccess access
Read or write.
Definition filesystem.h:108
std::string root
Canonical root the path lies outside.
Definition filesystem.h:106
std::string tool
Fully-qualified tool ("filesystem.write_file")
Definition filesystem.h:107
std::string path
Canonical absolute path the tool resolved.
Definition filesystem.h:105
PermissionsConfig permissions
Tool permissions.
Definition config.h:1342
ModelsConfig models
Tiers + router.
Definition config.h:1339
std::filesystem::path workspace_app_context
Per-workspace app_context file, relative to each workspace root (gh#179, v2.14.0).
Definition config.h:1381
MCPConfig mcp
MCP server settings.
Definition config.h:1343
std::filesystem::path config_dir
Config dir — base for bundled data discovery.
Definition config.h:1412
One workspace's assembled system prompts (gh#179, v2.14.0).
std::unordered_map< std::string, std::string > tier_prompts
Tier → system prompt.
std::string default_prompt
Untiered first turn.
std::string body
Markdown system prompt body.
Definition manager.h:113
Engine handle struct — owns all subsystems.
int log_id
gh#59 (v2.3.1): unique handle id for per-handle log routing via entropic::log::HandleAwareSink.
std::mutex workspace_mutex
gh#166: guards both maps above. Held for lookups only.
std::map< std::string, std::unique_ptr< EntropicWorkspace > > workspaces
gh#166: named workspaces, by name.
std::atomic< bool > configured
True after configure()
std::map< std::string, std::string > session_workspace
gh#166: session key → workspace name.
std::string last_error
Per-handle error message.
std::unique_ptr< entropic::AgentEngine > engine
Agentic loop (owns conversation state)
void * path_approval_data
Forwarded to the approver.
entropic::ParsedConfig config
Parsed config.
std::unique_ptr< entropic::ServerManager > server_manager
MCP server lifecycle.
std::mutex path_approval_mutex
Guards the pair above: the thunk reads it from run threads while the setter may write it from any thr...
ent_path_approval_cb path_approval_cb
Consumer's approver for filesystem access outside the root.