Entropic 2.11.1
Local-first agentic inference engine
Loading...
Searching...
No Matches
delegation.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
20
21#include <nlohmann/json.hpp>
22
23#include <algorithm>
24
25static auto logger = entropic::log::get("core.delegation");
26
27namespace entropic {
28
29// ── Construction ─────────────────────────────────────────
30
42 RunChildLoopFn run_child,
43 void* run_child_data,
44 const TierResolutionInterface& tier_resolution,
45 const std::filesystem::path& repo_dir,
46 SandboxManager* sandbox_mgr)
47 : run_child_fn_(run_child),
48 run_child_data_(run_child_data),
49 tier_res_(tier_resolution),
50 sandbox_mgr_(sandbox_mgr),
51 repo_dir_(repo_dir) {
52}
53
61 todo_callbacks_ = callbacks;
62}
63
72 ScopedSandbox::SwapDirFn swap_fn, void* user_data) {
73 swap_dir_fn_ = swap_fn;
74 swap_dir_data_ = user_data;
75}
76
84 storage_ = storage;
85}
86
96 ent_decision_t (*on_start)(const ent_delegation_request_t*, void*),
97 ent_decision_t (*on_complete)(const ent_delegation_result_t*, void*),
98 void* user_data) {
99 delegation_start_cb_ = on_start;
100 delegation_complete_cb_ = on_complete;
101 delegation_cb_data_ = user_data;
102}
103
121ent_decision_t DelegationManager::fire_start_cb(
122 const std::string& delegation_id,
123 const std::string& target_tier,
124 const std::string& task,
125 int depth,
126 bool is_pipeline) {
127 if (delegation_start_cb_ == nullptr) {
128 return ENT_DECISION_ACCEPT;
129 }
131 req.delegation_id = delegation_id.c_str();
132 req.target_tier = target_tier.c_str();
133 req.task = task.c_str();
134 req.depth = depth;
135 req.is_pipeline = is_pipeline ? 1 : 0;
136 // Exception shield: per docs/architecture-cpp.md design rule #6,
137 // exceptions do not cross .so boundaries. A buggy consumer
138 // throwing a C++ exception out of the callback would otherwise
139 // unwind through the engine's stack with undefined cleanup. Fail
140 // safe by treating a throw as REJECT (gh#29 hardening).
141 try {
142 return delegation_start_cb_(&req, delegation_cb_data_);
143 } catch (...) {
144 logger->warn("delegation_start_cb threw for {}; treating as "
145 "REJECT (gh#29 exception shield)",
146 delegation_id);
147 return ENT_DECISION_REJECT;
148 }
149}
150
179 const SandboxInfo& sb_info, const SandboxResult& sandbox_result,
180 const DelegationResult& result,
181 const std::vector<const char*>& files_c, size_t files_len) {
183 res.delegation_id = sb_info.delegation_id.c_str();
184 res.target_tier = result.target_tier.c_str();
185 res.success = result.success ? 1 : 0;
186 res.summary = result.summary.c_str();
187 res.patch = sandbox_result.patch.c_str();
188 res.patch_len = sandbox_result.patch.size();
189 res.files_touched = files_c.data();
190 res.files_touched_len = files_len;
191 return res;
192}
193
199void DelegationManager::deliver_sandbox_result(
200 const SandboxInfo& sb_info,
201 const SandboxResult& sandbox_result,
202 const DelegationResult& result) {
203
204 if (delegation_complete_cb_ == nullptr) {
205 persist_pending_patch(sb_info, sandbox_result,
206 "no complete callback registered");
207 return;
208 }
209
210 std::vector<std::string> files_owned;
211 files_owned.reserve(sandbox_result.files_touched.size());
212 for (const auto& p : sandbox_result.files_touched) {
213 files_owned.push_back(p.string());
214 }
215 std::vector<const char*> files_c;
216 files_c.reserve(files_owned.size() + 1);
217 for (const auto& s : files_owned) { files_c.push_back(s.c_str()); }
218 files_c.push_back(nullptr);
219
221 sb_info, sandbox_result, result, files_c, files_owned.size());
222
223 ent_decision_t decision =
224 invoke_complete_cb(res, sb_info.delegation_id);
225 if (decision == ENT_DECISION_REJECT) {
226 persist_pending_patch(sb_info, sandbox_result,
227 "consumer REJECTED");
228 } else {
229 logger->info("Delegation {}: consumer ACCEPTED ({} files, "
230 "{} bytes)",
231 sb_info.delegation_id,
232 sandbox_result.files_touched.size(),
233 sandbox_result.patch.size());
234 }
235}
236
245ent_decision_t DelegationManager::invoke_complete_cb(
246 const ent_delegation_result_t& res, const std::string& delegation_id) {
247 // Exception shield: a buggy consumer must never unwind through the
248 // engine. Treat throw as REJECT so the patch is preserved on disk
249 // for inspection (gh#29 hardening — same policy as fire_start_cb).
250 try {
251 return delegation_complete_cb_(&res, delegation_cb_data_);
252 } catch (...) {
253 logger->warn("delegation_complete_cb threw for {}; treating as "
254 "REJECT (patch preserved to pending/)", delegation_id);
255 return ENT_DECISION_REJECT;
256 }
257}
258
264void DelegationManager::persist_pending_patch(
265 const SandboxInfo& sb_info,
266 const SandboxResult& sandbox_result,
267 const char* reason) {
268 auto path = sandbox_mgr_->write_pending_patch(
269 sb_info.delegation_id, sandbox_result.patch);
270 if (path) {
271 logger->warn("Delegation {}: {}; patch saved to {} "
272 "({} files, {} bytes)",
273 sb_info.delegation_id, reason, path->string(),
274 sandbox_result.files_touched.size(),
275 sandbox_result.patch.size());
276 }
277}
278
279// ── Single delegation ────────────────────────────────────
280
306std::optional<DelegationResult>
307DelegationManager::check_delegation_preconditions(
308 const ChildContextInfo& info,
309 const std::string& target_tier,
310 const std::string& task,
311 const std::string& del_id,
312 int depth,
313 std::optional<SandboxInfo>& sb_info) {
314 std::optional<DelegationResult> early;
315 if (!info.valid) {
316 logger->error("Tier '{}' not found", target_tier);
317 early = DelegationResult{
318 "Unknown tier: " + target_tier, false, target_tier, task};
319 } else if (fire_start_cb(del_id, target_tier, task, depth, false)
320 == ENT_DECISION_REJECT) {
321 logger->info("Delegation {} ({}) rejected by start callback",
322 del_id, target_tier);
323 early = DelegationResult{
324 "Delegation rejected by consumer", false, target_tier, task};
325 } else if (sandbox_mgr_ != nullptr) {
326 sb_info = sandbox_mgr_->create_sandbox(del_id);
327 if (!sb_info.has_value()) {
328 // gh#33 bug 2 (v2.1.6): pre-2.1.6 a failed create_sandbox
329 // silently fell through to running the child against the
330 // parent's cwd and surfaced an opaque
331 // "(No response from delegate)" on later failure. The
332 // sandbox-unavailable mode is now consumer-visible.
333 logger->error(
334 "Delegation {} ({}): session sandbox unavailable",
335 del_id, target_tier);
336 early = DelegationResult{
337 "(DELEGATION FAILED: session sandbox unavailable)",
338 false, target_tier, task};
339 }
340 }
341 return early;
342}
343
355 LoopContext& parent_ctx,
356 const std::string& target_tier,
357 const std::string& task,
358 std::optional<int> max_turns) {
359
360 logger->info("Delegation: target_tier='{}', task='{}', depth={}",
361 target_tier, task,
362 parent_ctx.delegation_depth + 1);
363 auto info = tier_res_.resolve_tier
364 ? tier_res_.resolve_tier(target_tier, tier_res_.user_data)
366
367 std::string del_id =
368 "d" + std::to_string(parent_ctx.delegation_depth + 1);
369 std::optional<SandboxInfo> sb_info;
370 if (auto early = check_delegation_preconditions(
371 info, target_tier, task, del_id,
372 parent_ctx.delegation_depth + 1, sb_info)) {
373 return *early;
374 }
375
376 auto child_ctx = build_child_context(parent_ctx, info, task);
377 child_ctx.locked_tier = target_tier;
378
379 DelegationResult result;
380
381 if (sb_info && swap_dir_fn_ != nullptr) {
382 ScopedSandbox scope(swap_dir_fn_, swap_dir_data_,
383 sb_info->path, repo_dir_);
384 result = run_child(child_ctx, target_tier, task, max_turns);
385 } else {
386 result = run_child(child_ctx, target_tier, task, max_turns);
387 }
388
389 finalize_sandbox_for(sb_info, result);
390 return result;
391}
392
418LoopContext DelegationManager::build_resumed_child_context(
419 const LoopContext& parent_ctx,
420 const ChildContextInfo& info,
421 const std::string& target_tier,
422 const std::string& task,
423 std::vector<Message> seed_history) {
424 LoopContext child_ctx;
425 child_ctx.delegation_depth = parent_ctx.delegation_depth + 1;
426 child_ctx.delegation_ancestor_tiers =
427 parent_ctx.delegation_ancestor_tiers;
428 child_ctx.delegation_ancestor_tiers.push_back(target_tier);
429 child_ctx.parent_conversation_id = parent_ctx.conversation_id;
430 child_ctx.all_tools = info.tools;
431 child_ctx.active_phase = "default";
432 child_ctx.locked_tier = target_tier;
433 child_ctx.messages = std::move(seed_history);
434 bool has_system = !child_ctx.messages.empty()
435 && child_ctx.messages.front().role == "system";
436 if (!has_system && !info.system_prompt.empty()) {
437 Message sys;
438 sys.role = "system";
439 sys.content = info.system_prompt;
440 child_ctx.messages.insert(child_ctx.messages.begin(),
441 std::move(sys));
442 }
443 Message user;
444 user.role = "user";
445 user.content = task;
446 if (!info.completion_instructions.empty()) {
447 user.content += "\n\n" + info.completion_instructions;
448 }
449 child_ctx.messages.push_back(std::move(user));
450 return child_ctx;
451}
452
461 LoopContext& parent_ctx,
462 const std::string& target_tier,
463 const std::string& task,
464 std::vector<Message> seed_history,
465 std::optional<int> max_turns) {
466
467 logger->info("Resume delegation: target_tier='{}' task='{}' "
468 "history_messages={}",
469 target_tier, task, seed_history.size());
470 auto info = tier_res_.resolve_tier
471 ? tier_res_.resolve_tier(target_tier, tier_res_.user_data)
473
474 std::string del_id =
475 "d" + std::to_string(parent_ctx.delegation_depth + 1) + "r";
476 std::optional<SandboxInfo> sb_info;
477 if (auto early = check_delegation_preconditions(
478 info, target_tier, task, del_id,
479 parent_ctx.delegation_depth + 1, sb_info)) {
480 return *early;
481 }
482
483 auto child_ctx = build_resumed_child_context(
484 parent_ctx, info, target_tier, task, std::move(seed_history));
485
486 DelegationResult result;
487 if (sb_info && swap_dir_fn_ != nullptr) {
488 ScopedSandbox scope(swap_dir_fn_, swap_dir_data_,
489 sb_info->path, repo_dir_);
490 result = run_child(child_ctx, target_tier, task, max_turns);
491 } else {
492 result = run_child(child_ctx, target_tier, task, max_turns);
493 }
494 finalize_sandbox_for(sb_info, result);
495 return result;
496}
497
498// ── Pipeline ─────────────────────────────────────────────
499
521static std::string pipeline_context(
522 size_t stage_idx, size_t total,
523 const std::vector<std::string>& stages,
524 const std::string& prior_output) {
525 std::string ctx = "[PIPELINE CONTEXT]\n";
526 ctx += "Stage " + std::to_string(stage_idx + 1) +
527 " of " + std::to_string(total) + "\n";
528 ctx += "Role: " + stages[stage_idx] + "\n";
529 ctx += "Stay within your role. Do not perform work "
530 "outside your stage's responsibility.\n";
531 if (!prior_output.empty()) {
532 ctx += "\n[PRIOR STAGE OUTPUT]\n";
533 ctx += prior_output;
534 ctx += "\n";
535 }
536 ctx += "\n";
537 return ctx;
538}
539
557 LoopContext& parent_ctx,
558 const std::vector<std::string>& stages,
559 const std::string& task,
560 std::vector<DelegationResult>& stage_log) {
561
562 logger->info("Pipeline: {} stages, task='{}'", stages.size(), task);
563
564 // gh#29 (v2.1.5): single pre-flight gate for the whole pipeline.
565 auto first = stages.empty() ? std::string{} : stages.front();
566 if (fire_start_cb("pipeline", first, task,
567 parent_ctx.delegation_depth + 1, true)
568 == ENT_DECISION_REJECT) {
569 logger->info("Pipeline rejected by start callback");
570 return {"Pipeline rejected by consumer", false, first, task};
571 }
572
573 // Shared sandbox for the entire pipeline (gh#29). Each stage runs
574 // inside the same directory so later stages observe earlier stages'
575 // file edits — preserving the v2.1.4 forward-carry behavior.
576 std::optional<SandboxInfo> shared_sb;
577 if (sandbox_mgr_ != nullptr) {
578 shared_sb = sandbox_mgr_->create_sandbox("pipeline");
579 if (!shared_sb.has_value()) {
580 // gh#33 bug 2 (v2.1.6): see execute_delegation comment.
581 logger->error(
582 "Pipeline ({} stages): session sandbox unavailable",
583 stages.size());
584 return {"(DELEGATION FAILED: session sandbox unavailable)",
585 false, first, task};
586 }
587 }
588
589 DelegationResult last_result;
590 last_result.task = task;
591
592 for (size_t i = 0; i < stages.size(); ++i) {
593 if (!run_pipeline_stage(parent_ctx, stages, i, task,
594 shared_sb, stage_log, last_result)) {
595 break;
596 }
597 }
598
599 finalize_sandbox_for(shared_sb, last_result);
600 return last_result;
601}
602
622bool DelegationManager::run_pipeline_stage(
623 LoopContext& parent_ctx,
624 const std::vector<std::string>& stages,
625 size_t stage_idx,
626 const std::string& task,
627 const std::optional<SandboxInfo>& shared_sb,
628 std::vector<DelegationResult>& stage_log,
629 DelegationResult& last_result) {
630
631 const auto& tier_name = stages[stage_idx];
632 std::string prior = (stage_idx == 0) ? std::string{} : last_result.summary;
633 std::string stage_task =
634 pipeline_context(stage_idx, stages.size(), stages, prior) + task;
635
636 auto info = tier_res_.resolve_tier
637 ? tier_res_.resolve_tier(tier_name, tier_res_.user_data)
638 : ChildContextInfo{};
639
640 if (!info.valid) {
641 logger->error("Pipeline stage {}: tier '{}' not found",
642 stage_idx, tier_name);
643 last_result.success = false;
644 last_result.summary = "Unknown tier: " + tier_name;
645 return false;
646 }
647
648 auto child_ctx = build_child_context(parent_ctx, info, stage_task);
649 child_ctx.locked_tier = tier_name;
650
651 if (shared_sb && swap_dir_fn_ != nullptr) {
652 ScopedSandbox scope(swap_dir_fn_, swap_dir_data_,
653 shared_sb->path, repo_dir_);
654 last_result = run_child(child_ctx, tier_name, stage_task,
655 std::nullopt);
656 } else {
657 last_result = run_child(child_ctx, tier_name, stage_task,
658 std::nullopt);
659 }
660
661 stage_log.push_back(last_result);
662 logger->info("Pipeline stage {} ({}): {}", stage_idx, tier_name,
663 last_result.success ? "complete" : "failed");
664 return last_result.success;
665}
666
667// ── Child context ────────────────────────────────────────
668
678LoopContext DelegationManager::build_child_context(
679 const LoopContext& parent_ctx,
680 const ChildContextInfo& info,
681 const std::string& task) {
682
683 LoopContext child;
684 child.delegation_depth = parent_ctx.delegation_depth + 1;
685 // gh#48 (regression fix, v2.7.4): propagate the parent conversation id so
686 // the storage create_delegation INSERT satisfies the delegations→
687 // conversations FK. Without this the row is rejected, the delegations
688 // table stays empty, and entropic.followup returns the no-matches
689 // sentinel. (Present in build_resumed_child_context; lost here when the
690 // child-context builders were extracted.)
691 child.parent_conversation_id = parent_ctx.conversation_id;
692 // P1-9: propagate ancestor chain + append parent tier so the
693 // child can reject cycles (A→B→A) before executing.
694 child.delegation_ancestor_tiers = parent_ctx.delegation_ancestor_tiers;
695 if (!parent_ctx.locked_tier.empty()) {
696 child.delegation_ancestor_tiers.push_back(parent_ctx.locked_tier);
697 }
698 child.locked_tier = info.system_prompt.empty()
699 ? parent_ctx.locked_tier : "";
700 child.all_tools = info.tools;
701 child.active_phase = "default";
702
703 // System prompt as first message
704 Message sys;
705 sys.role = "system";
706 sys.content = info.system_prompt;
707 child.messages.push_back(std::move(sys));
708
709 // Task as user message (with completion instructions)
710 std::string user_content = task;
711 if (!info.completion_instructions.empty()) {
712 user_content += "\n\n" + info.completion_instructions;
713 }
714 Message user;
715 user.role = "user";
716 user.content = std::move(user_content);
717 child.messages.push_back(std::move(user));
718
719 return child;
720}
721
729std::string DelegationManager::extract_summary(
730 const LoopContext& child_ctx) const {
731 // Prefer explicit completion summary
732 auto it = child_ctx.metadata.find("explicit_completion_summary");
733 if (it != child_ctx.metadata.end() && !it->second.empty()) {
734 return mcp::sanitize_utf8(it->second);
735 }
736
737 // Fall back to last assistant message
738 for (auto rit = child_ctx.messages.rbegin();
739 rit != child_ctx.messages.rend(); ++rit) {
740 if (rit->role == "assistant" && !rit->content.empty()) {
741 return mcp::sanitize_utf8(rit->content);
742 }
743 }
744
745 return "(No response from delegate)";
746}
747
768std::string DelegationManager::create_storage_record(
769 LoopContext& child_ctx, const std::string& target_tier,
770 const std::string& task, std::optional<int> max_turns) {
771 if (!storage_ || !storage_->create_delegation) {
772 return "";
773 }
774 std::string del_id, child_conv_id;
775 auto src_tier = child_ctx.locked_tier.empty()
776 ? "root" : child_ctx.locked_tier.c_str();
777 storage_->create_delegation(
778 child_ctx.parent_conversation_id.c_str(),
779 src_tier, target_tier.c_str(), task.c_str(),
780 max_turns.value_or(0), del_id, child_conv_id,
781 storage_->user_data);
782 child_ctx.conversation_id = child_conv_id;
783 return del_id;
784}
785
793void DelegationManager::complete_storage_record(
794 const std::string& delegation_id,
795 const DelegationResult& result) {
796 if (!storage_ || !storage_->complete_delegation
797 || delegation_id.empty()) {
798 return;
799 }
800 const char* status = result.success ? "completed" : "failed";
801 storage_->complete_delegation(
802 delegation_id.c_str(), status,
803 result.summary.c_str(), storage_->user_data);
804}
805
816DelegationResult DelegationManager::run_child(
817 LoopContext& child_ctx,
818 const std::string& target_tier,
819 const std::string& task,
820 std::optional<int> max_turns) {
821
822 // Save parent todo list
823 std::string saved_todo;
824 if (todo_callbacks_.save != nullptr) {
825 saved_todo = todo_callbacks_.save(todo_callbacks_.user_data);
826 }
827 if (todo_callbacks_.install_fresh != nullptr) {
828 todo_callbacks_.install_fresh(todo_callbacks_.user_data);
829 }
830
831 auto delegation_id = create_storage_record(
832 child_ctx, target_tier, task, max_turns);
833
834 logger->info("Running child loop: tier={} depth={} msgs={} "
835 "system_hash={:016x}",
836 target_tier, child_ctx.delegation_depth,
837 child_ctx.messages.size(),
838 std::hash<std::string>{}(
839 child_ctx.messages.empty()
840 ? std::string{}
841 : child_ctx.messages[0].content));
842
843 if (run_child_fn_ != nullptr) {
844 run_child_fn_(child_ctx, run_child_data_);
845 }
846
847 // Restore parent todo list
848 if (todo_callbacks_.restore != nullptr && !saved_todo.empty()) {
849 todo_callbacks_.restore(saved_todo, todo_callbacks_.user_data);
850 }
851
852 auto result = build_child_result(
853 target_tier, task, child_ctx);
854 complete_storage_record(delegation_id, result);
855 log_child_result(result);
856 return result;
857}
858
880DelegationResult DelegationManager::build_child_result(
881 const std::string& target_tier,
882 const std::string& task,
883 LoopContext& child_ctx) {
884 DelegationResult result;
885 result.target_tier = target_tier;
886 result.task = task;
887 auto tr = child_ctx.metadata.find("terminal_reason");
888 if (tr != child_ctx.metadata.end()) {
889 result.terminal_reason = tr->second;
890 }
891 result.success = (child_ctx.state == AgentState::COMPLETE
892 && result.terminal_reason.empty());
893 result.turns_used = child_ctx.metrics.iterations;
894 result.summary = extract_summary(child_ctx);
895 // Issue #10 (v2.1.4): hoist coverage_gap signal from child
896 // metadata onto DelegationResult so the parent's
897 // finalize_delegation_result can branch on it without re-parsing
898 // ctx.metadata. dir_complete writes these keys when the child
899 // calls entropic.complete with coverage_gap=true.
900 auto cg = child_ctx.metadata.find("coverage_gap");
901 if (cg != child_ctx.metadata.end() && cg->second == "true") {
902 result.coverage_gap = true;
903 auto gd = child_ctx.metadata.find("gap_description");
904 if (gd != child_ctx.metadata.end()) {
905 result.gap_description = gd->second;
906 }
907 auto sf = child_ctx.metadata.find("suggested_files_json");
908 if (sf != child_ctx.metadata.end()) {
909 auto parsed = nlohmann::json::parse(
910 sf->second, nullptr, false);
911 if (parsed.is_array()) {
912 result.suggested_files =
913 parsed.get<std::vector<std::string>>();
914 }
915 }
916 }
917 result.child_messages = std::move(child_ctx.messages);
918 return result;
919}
920
927void DelegationManager::log_child_result(
928 const DelegationResult& result) {
929 if (result.terminal_reason.empty()) {
930 logger->info("Child loop done: tier={} success={} turns={}",
931 result.target_tier, result.success,
932 result.turns_used);
933 } else {
934 logger->warn("Child loop done: tier={} success=false "
935 "turns={} reason={}",
936 result.target_tier, result.turns_used,
937 result.terminal_reason);
938 }
939}
940
956void DelegationManager::finalize_sandbox_for(
957 const std::optional<SandboxInfo>& sb_info,
958 const DelegationResult& result) {
959 if (!sb_info || !sandbox_mgr_) {
960 return;
961 }
962
963 if (result.success) {
964 auto patch_result = sandbox_mgr_->finalize_sandbox(*sb_info);
965 if (patch_result) {
966 deliver_sandbox_result(*sb_info, *patch_result, result);
967 } else {
968 logger->error("Delegation {}: finalize_sandbox failed; "
969 "no patch delivered", sb_info->delegation_id);
970 }
971 } else {
972 logger->info("Delegation {} failed: discarding sandbox without "
973 "generating patch", sb_info->delegation_id);
974 }
975 sandbox_mgr_->discard_sandbox(*sb_info);
976}
977
978} // namespace entropic
DelegationResult execute_delegation(LoopContext &parent_ctx, const std::string &target_tier, const std::string &task, std::optional< int > max_turns=std::nullopt)
Run a child inference loop for the target tier.
DelegationResult execute_pipeline(LoopContext &parent_ctx, const std::vector< std::string > &stages, const std::string &task, std::vector< DelegationResult > &stage_log)
Run a multi-stage delegation pipeline sequentially.
void set_todo_callbacks(const TodoCallbacks &callbacks)
Set todo list save/restore callbacks.
DelegationResult execute_resume_delegation(LoopContext &parent_ctx, const std::string &target_tier, const std::string &task, std::vector< Message > seed_history, std::optional< int > max_turns=std::nullopt)
Resume a prior delegation with pre-loaded conversation history.
void set_dir_swap(ScopedSandbox::SwapDirFn swap_fn, void *user_data)
Set directory swap callback for ScopedSandbox.
DelegationManager(RunChildLoopFn run_child, void *run_child_data, const TierResolutionInterface &tier_resolution, const std::filesystem::path &repo_dir={}, SandboxManager *sandbox_mgr=nullptr)
Construct with engine loop callback and tier resolution.
void set_storage(const struct StorageInterface *storage)
Set storage interface for delegation record persistence.
void set_delegation_callbacks(ent_decision_t(*on_start)(const ent_delegation_request_t *, void *), ent_decision_t(*on_complete)(const ent_delegation_result_t *, void *), void *user_data)
Set delegation start/complete callbacks (gh#29, v2.1.5).
Create, finalize, and discard per-delegation filesystem sandboxes.
Definition sandbox.h:99
void discard_sandbox(const SandboxInfo &info)
Remove a sandbox directory.
Definition sandbox.cpp:533
std::optional< SandboxResult > finalize_sandbox(const SandboxInfo &info)
Produce the final patch artifact for a sandbox.
Definition sandbox.cpp:506
std::optional< SandboxInfo > create_sandbox(const std::string &delegation_id, std::optional< SandboxInfo > chain_from=std::nullopt)
Create a new delegation sandbox.
Definition sandbox.cpp:390
std::optional< std::filesystem::path > write_pending_patch(const std::string &delegation_id, const std::string &patch)
Write a patch to the session's pending/ directory.
Definition sandbox.cpp:552
RAII directory swapper for sandbox-scoped tool execution.
Definition sandbox.h:285
void(*)(const std::filesystem::path &path, void *user_data) SwapDirFn
Callback type for directory swapping.
Definition sandbox.h:298
DelegationManager — child loop creation and execution.
Types for the agentic loop engine.
ent_decision_t
Consumer decision returned from delegation callbacks.
Definition entropic.h:1091
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
Activate model on GPU (WARM → ACTIVE).
static std::string pipeline_context(size_t stage_idx, size_t total, const std::vector< std::string > &stages, const std::string &prior_output)
Build pipeline context prefix for a stage.
static ent_delegation_result_t build_delegation_result_struct(const SandboxInfo &sb_info, const SandboxResult &sandbox_result, const DelegationResult &result, const std::vector< const char * > &files_c, size_t files_len)
Deliver a finalized patch to consumer or pending/.
void(*)(LoopContext &ctx, void *user_data) RunChildLoopFn
Callback type for running a child engine loop.
Definition delegation.h:70
Request describing a delegation that is about to run.
Definition entropic.h:1105
const char * delegation_id
Short id ("d1", "d2", "pipeline")
Definition entropic.h:1106
Result of a finalized delegation, delivered to the consumer.
Definition entropic.h:1128
const char * delegation_id
Short id (matches request)
Definition entropic.h:1129
Resolved tier information for building child delegation contexts.
Result returned from a child delegation loop.
Definition delegation.h:33
bool success
Whether child reached COMPLETE via real entropic.complete.
Definition delegation.h:35
std::string summary
Final summary from child.
Definition delegation.h:34
std::string task
Original task text.
Definition delegation.h:37
std::string target_tier
Tier that executed.
Definition delegation.h:36
Mutable state carried through the agentic loop.
std::vector< std::string > delegation_ancestor_tiers
Tier stack from root to this loop (P1-9, 2.0.6-rc16)
std::string active_phase
Active inference phase.
std::string conversation_id
Conversation ID for storage (v1.8.8)
int delegation_depth
0 = root, 1+ = child
std::vector< Message > messages
Conversation history.
std::string locked_tier
Tier locked for this loop ("" = none)
std::string parent_conversation_id
Parent conv ID (delegation)
std::vector< std::string > all_tools
Full tool list as raw JSON strings.
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
Identifies one delegation's sandbox directory.
Definition sandbox.h:49
std::string delegation_id
Short delegation id (e.g. "d1", "pipeline")
Definition sandbox.h:51
Final artifact emitted by a finalized sandbox.
Definition sandbox.h:66
std::string patch
Unified diff text.
Definition sandbox.h:67
Storage interface for conversation persistence.
bool(* 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)
Create a delegation record with child conversation.
bool(* complete_delegation)(const char *delegation_id, const char *status, const char *summary, void *user_data)
Complete a delegation record.
void * user_data
Opaque pointer (storage backend)
Tier resolution callbacks for delegation and auto-chain.
void * user_data
Opaque pointer (facade context)
ChildContextInfo(* resolve_tier)(const std::string &tier_name, void *user_data)
Build context info for a child delegation to the given tier.
Callback type for saving/restoring todo list state.
Definition delegation.h:79
std::string(* save)(void *user_data)
Save current todo list state. Returns opaque state string.
Definition delegation.h:81
void(* restore)(const std::string &saved, void *user_data)
Restore a previously saved todo list state.
Definition delegation.h:85
void(* install_fresh)(void *user_data)
Install a fresh empty todo list for child.
Definition delegation.h:83
void * user_data
Opaque pointer (facade context)
Definition delegation.h:86
UTF-8 validation + replacement at every system boundary where bytes change ownership.