Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
identity_manager.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
10
11#include <algorithm>
12#include <filesystem>
13#include <regex>
14
15namespace entropic {
16
17static auto logger = log::get("identity_manager");
18
20static const std::vector<std::string> s_reserved_names = {
21 "default", "none", "all", "router"};
22
28static const std::regex s_name_regex("^[a-z][a-z0-9_-]{0,63}$");
29
30// ── Phase validation (extracted for return count) ────────
31
39static std::string validate_phases(
40 const std::unordered_map<std::string,
41 PhaseConfig>& phases) {
42 for (const auto& [phase_name, phase] : phases) {
43 if (phase_name.empty()) {
44 return "Phase name cannot be empty";
45 }
46 if (phase.max_output_tokens <= 0) {
47 return "Phase '" + phase_name
48 + "' max_output_tokens must be > 0";
49 }
50 }
51 return "";
52}
53
54// ── Field validation ─────────────────────────────────────
55
63static std::string validate_fields(const IdentityConfig& config) {
64 auto phase_err = validate_phases(config.phases);
65 if (!phase_err.empty()) { return phase_err; }
66 bool bad_adapter = !config.adapter_path.empty()
67 && !std::filesystem::exists(config.adapter_path);
68 return bad_adapter
69 ? "Adapter path not found: " + config.adapter_path : "";
70}
71
72// ── Create precondition check (extracted for return count) ──
73
84 const IdentityConfig& config,
85 const IdentityManagerConfig& mgr_config,
86 const std::unordered_map<std::string, IdentityConfig>& identities) {
87 if (!mgr_config.allow_dynamic) {
88 logger->warn("Dynamic identity creation disabled");
90 }
91 if (identities.size() >= mgr_config.max_identities) {
92 logger->warn("Identity limit reached: {}",
93 mgr_config.max_identities);
95 }
96 bool exists = identities.count(config.name) > 0;
97 if (exists) {
98 logger->warn("Identity already exists: {}", config.name);
99 }
101}
102
103// ── Mutation precondition check (update/destroy) ─────────
104
115 const std::string& name,
116 const std::unordered_map<std::string, IdentityConfig>& identities,
117 std::unordered_map<std::string,
118 IdentityConfig>::const_iterator& out_it) {
119 out_it = identities.find(name);
120 if (out_it == identities.end()) {
122 }
123 return (out_it->second.origin == IdentityOrigin::STATIC)
125}
126
127// ── Constructor ──────────────────────────────────────────
128
136 : config_(config) {}
137
138// ── Interface setters ────────────────────────────────────
139
147 const GrammarValidationInterface& iface) {
148 grammar_iface_ = iface;
149}
150
158 mcp_iface_ = iface;
159}
160
161// ── load_static ──────────────────────────────────────────
162
171 const std::vector<IdentityConfig>& identities) {
172 std::unique_lock lock(identities_mutex_);
173 size_t loaded = 0;
174 for (const auto& id : identities) {
175 IdentityConfig cfg = id;
177 identities_[cfg.name] = std::move(cfg);
178 ++loaded;
179 }
180 logger->info("Loaded {} static identities", loaded);
181 router_dirty_.store(true, std::memory_order_release);
182 fire_cache_invalidator(); // P1-7: identity change → drop prompt caches
183 return loaded;
184}
185
186// ── create ───────────────────────────────────────────────
187
196 std::unique_lock lock(identities_mutex_);
198 config, config_, identities_);
199 if (pre != ENTROPIC_OK) { return pre; }
200 auto err = validate(config, false);
201 if (!err.empty()) {
202 logger->warn("Identity validation failed: {}", err);
204 }
205 IdentityConfig cfg = config;
207 register_mcp_keys(cfg);
208 identities_[cfg.name] = std::move(cfg);
209 router_dirty_.store(true, std::memory_order_release);
210 fire_cache_invalidator(); // P1-7: identity change → drop prompt caches
211 logger->info("Identity created: name='{}', routable={}, "
212 "prompt={} chars",
213 config.name, config.routable,
214 config.system_prompt.size());
215 return ENTROPIC_OK;
216}
217
218// ── update ───────────────────────────────────────────────
219
229 const std::string& name,
230 const IdentityConfig& config) {
231 std::unique_lock lock(identities_mutex_);
232 std::unordered_map<std::string,
233 IdentityConfig>::const_iterator it;
234 auto pre = check_mutable(name, identities_, it);
235 if (pre != ENTROPIC_OK) { return pre; }
236 auto err = validate(config, true);
237 if (!err.empty()) {
238 logger->warn("Identity update validation failed: {}", err);
240 }
241 unregister_mcp_keys(name);
242 IdentityConfig cfg = config;
244 cfg.name = name;
245 register_mcp_keys(cfg);
246 identities_[name] = std::move(cfg);
247 router_dirty_.store(true, std::memory_order_release);
248 fire_cache_invalidator(); // P1-7: identity change → drop prompt caches
249 logger->info("Dynamic identity updated: {}", name);
250 return ENTROPIC_OK;
251}
252
253// ── destroy ──────────────────────────────────────────────
254
263 std::unique_lock lock(identities_mutex_);
264 std::unordered_map<std::string,
265 IdentityConfig>::const_iterator it;
266 auto pre = check_mutable(name, identities_, it);
267 if (pre != ENTROPIC_OK) { return pre; }
268 if (in_use_checker_ && in_use_checker_(name, in_use_user_data_)) {
270 }
271 unregister_mcp_keys(name);
272 identities_.erase(it);
273 router_dirty_.store(true, std::memory_order_release);
274 fire_cache_invalidator(); // P1-7: identity change → drop prompt caches
275 logger->info("Dynamic identity destroyed: {}", name);
276 return ENTROPIC_OK;
277}
278
279// ── get ──────────────────────────────────────────────────
280
289 const std::string& name) const {
290 std::shared_lock lock(identities_mutex_);
291 auto it = identities_.find(name);
292 return (it != identities_.end()) ? &it->second : nullptr;
293}
294
295// ── has ──────────────────────────────────────────────────
296
304bool IdentityManager::has(const std::string& name) const {
305 std::shared_lock lock(identities_mutex_);
306 return identities_.count(name) > 0;
307}
308
309// ── list ─────────────────────────────────────────────────
310
317std::vector<std::string> IdentityManager::list() const {
318 std::shared_lock lock(identities_mutex_);
319 std::vector<std::string> names;
320 names.reserve(identities_.size());
321 for (const auto& [k, _] : identities_) {
322 names.push_back(k);
323 }
324 return names;
325}
326
327// ── list_routable ────────────────────────────────────────
328
335std::vector<const IdentityConfig*>
337 std::shared_lock lock(identities_mutex_);
338 std::vector<const IdentityConfig*> result;
339 for (const auto& [_, cfg] : identities_) {
340 if (cfg.routable && !cfg.interstitial) {
341 result.push_back(&cfg);
342 }
343 }
344 return result;
345}
346
347// ── count / count_dynamic ────────────────────────────────
348
356 std::shared_lock lock(identities_mutex_);
357 return identities_.size();
358}
359
367 std::shared_lock lock(identities_mutex_);
368 size_t n = 0;
369 for (const auto& [_, cfg] : identities_) {
370 if (cfg.origin == IdentityOrigin::DYNAMIC) {
371 ++n;
372 }
373 }
374 return n;
375}
376
377// ── Router dirty flag ────────────────────────────────────
378
386 return router_dirty_.load(std::memory_order_acquire);
387}
388
395 router_dirty_.store(false, std::memory_order_release);
396}
397
398// ── In-use checker ───────────────────────────────────────
399
408 bool (*checker)(const std::string& name, void* user_data),
409 void* user_data) {
410 in_use_checker_ = checker;
411 in_use_user_data_ = user_data;
412}
413
427 void* user_data) {
428 cache_invalidator_ = cb;
429 cache_invalidator_data_ = user_data;
430}
431
437void IdentityManager::fire_cache_invalidator() {
438 if (cache_invalidator_ != nullptr) {
439 cache_invalidator_(cache_invalidator_data_);
440 }
441}
442
443// ── validate ─────────────────────────────────────────────
444
452std::string IdentityManager::validate_name(const std::string& name) {
453 if (name.empty()) {
454 return "Identity name cannot be empty";
455 }
456 if (!std::regex_match(name, s_name_regex)) {
457 return "Identity name must match ^[a-z][a-z0-9_-]{0,63}$";
458 }
459 bool reserved = std::find(
460 s_reserved_names.begin(), s_reserved_names.end(), name)
461 != s_reserved_names.end();
462 return reserved
463 ? "Identity name '" + name + "' is reserved" : "";
464}
465
474std::string IdentityManager::validate(
475 const IdentityConfig& config, bool is_update) const {
476 if (!is_update) {
477 auto name_err = validate_name(config.name);
478 if (!name_err.empty()) { return name_err; }
479 }
480 if (config.focus.empty()) {
481 return "Identity must have at least one focus keyword";
482 }
483 bool grammar_missing = !config.grammar_id.empty()
484 && grammar_iface_.has_grammar
485 && !grammar_iface_.has_grammar(
486 config.grammar_id, grammar_iface_.user_data);
487 return grammar_missing
488 ? "Grammar '" + config.grammar_id + "' not found in registry"
489 : validate_fields(config);
490}
491
492// ── MCP key registration ─────────────────────────────────
493
500void IdentityManager::register_mcp_keys(const IdentityConfig& config) {
501 if (config.mcp_keys.empty()) { return; }
502 if (!mcp_iface_.register_identity) { return; }
503 mcp_iface_.register_identity(config.name, mcp_iface_.user_data);
504 for (const auto& key : config.mcp_keys) {
505 if (mcp_iface_.grant) {
506 mcp_iface_.grant(config.name, key.tool_pattern,
507 static_cast<int>(key.level),
508 mcp_iface_.user_data);
509 }
510 }
511}
512
519void IdentityManager::unregister_mcp_keys(const std::string& name) {
520 if (!mcp_iface_.is_enforced || !mcp_iface_.unregister_identity) {
521 return;
522 }
523 if (mcp_iface_.is_enforced(name, mcp_iface_.user_data)) {
524 mcp_iface_.unregister_identity(name, mcp_iface_.user_data);
525 }
526}
527
528} // namespace entropic
size_t count() const
Get the total number of identities.
void set_mcp_interface(const MCPKeyInterface &iface)
Set MCP key management interface.
size_t count_dynamic() const
Get the number of dynamic identities.
void set_cache_invalidator(void(*cb)(void *user_data), void *user_data)
Set a callback invoked whenever an identity changes.
const IdentityConfig * get(const std::string &name) const
Get identity config by name.
IdentityManager(const IdentityManagerConfig &config)
Construct with configuration.
bool is_router_dirty() const
Check if the router classification prompt needs rebuilding.
entropic_error_t destroy(const std::string &name)
Destroy a dynamic identity.
void clear_router_dirty()
Clear the dirty flag (called after router prompt rebuild).
entropic_error_t update(const std::string &name, const IdentityConfig &config)
Update an existing dynamic identity.
size_t load_static(const std::vector< IdentityConfig > &identities)
Load static identities from config loader.
bool has(const std::string &name) const
Check if an identity exists.
void set_in_use_checker(bool(*checker)(const std::string &name, void *user_data), void *user_data)
Set the in-use checker callback.
std::vector< const IdentityConfig * > list_routable() const
List only routable identities (for classification prompt).
void set_grammar_interface(const GrammarValidationInterface &iface)
Set grammar validation interface.
entropic_error_t create(const IdentityConfig &config)
Create a new dynamic identity.
std::vector< std::string > list() const
List all identity names.
entropic_error_t
Error codes returned by all C API functions.
Definition error.h:35
@ ENTROPIC_OK
Success.
Definition error.h:36
@ ENTROPIC_ERROR_IN_USE
Resource is currently active and cannot be removed (v1.9.6)
Definition error.h:72
@ ENTROPIC_ERROR_ALREADY_EXISTS
Named resource already exists (v1.9.6)
Definition error.h:71
@ ENTROPIC_ERROR_IDENTITY_NOT_FOUND
Identity name not in config (v1.8.9)
Definition error.h:58
@ ENTROPIC_ERROR_LIMIT_REACHED
Resource limit exceeded (e.g., max_identities) (v1.9.6)
Definition error.h:70
@ ENTROPIC_ERROR_INVALID_CONFIG
Config validation failed (missing fields, bad values)
Definition error.h:38
@ ENTROPIC_ERROR_PERMISSION_DENIED
Tool call blocked by permission manager.
Definition error.h:44
IdentityManager – lifecycle management for static and dynamic identities.
spdlog initialization and logger access.
Activate model on GPU (WARM → ACTIVE).
static const std::regex s_name_regex("^[a-z][a-z0-9_-]{0,63}$")
Compiled regex for identity name validation.
static entropic_error_t check_mutable(const std::string &name, const std::unordered_map< std::string, IdentityConfig > &identities, std::unordered_map< std::string, IdentityConfig >::const_iterator &out_it)
Check that a mutable identity exists and is dynamic.
@ DYNAMIC
Created at runtime via API.
@ STATIC
Loaded from YAML frontmatter file at startup.
static std::string validate_phases(const std::unordered_map< std::string, PhaseConfig > &phases)
Validate all phases in an identity config.
static std::string validate_fields(const IdentityConfig &config)
Validate phases and adapter_path fields.
static const std::vector< std::string > s_reserved_names
Reserved identity names that cannot be used.
static entropic_error_t check_create_preconditions(const IdentityConfig &config, const IdentityManagerConfig &mgr_config, const std::unordered_map< std::string, IdentityConfig > &identities)
Check create preconditions before validation.
Grammar validation callbacks injected by the facade.
void * user_data
Opaque pointer (GrammarRegistry*)
bool(* has_grammar)(const std::string &key, void *user_data)
Check if a grammar key exists in the registry.
Full identity configuration.
Definition identity.h:48
bool routable
Participates in tier routing.
Definition identity.h:64
std::string name
Unique identity name (e.g., "eng", "npc_blacksmith")
Definition identity.h:49
IdentityOrigin origin
How this identity was created.
Definition identity.h:67
std::string adapter_path
LoRA adapter path (v1.9.2, empty = base model)
Definition identity.h:58
std::string system_prompt
Full system prompt text (markdown body)
Definition identity.h:50
std::unordered_map< std::string, PhaseConfig > phases
Named inference phases.
Definition identity.h:66
Configuration for the identity manager.
size_t max_identities
Maximum total identities (static + dynamic)
bool allow_dynamic
Master toggle for dynamic identity creation.
MCP key management callbacks injected by the facade.
void(* unregister_identity)(const std::string &name, void *user_data)
Remove an identity's key set.
bool(* is_enforced)(const std::string &name, void *user_data)
Check if an identity has a key set registered.
void(* register_identity)(const std::string &name, void *user_data)
Register an empty key set for an identity.
void * user_data
Opaque pointer (MCPAuthorizationManager*)
void(* grant)(const std::string &name, const std::string &pattern, int level, void *user_data)
Grant a key pattern to an identity.
Inference parameters for a single identity phase.
Definition config.h:772