Entropic 2.14.3
Local-first agentic inference engine
Loading...
Searching...
No Matches
external_bridge.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
41#pragma once
42
45
46#include <nlohmann/json_fwd.hpp>
47
48#include <atomic>
49#include <chrono>
50#include <condition_variable>
51#include <filesystem>
52#include <memory>
53#include <mutex>
54#include <map>
55#include <set>
56#include <string>
57#include <thread>
58#include <unordered_map>
59#include <unordered_set>
60#include <vector>
61
62// Forward declaration — bridge holds a raw pointer to the opaque handle.
63struct entropic_engine;
65
66namespace entropic {
67
88class ENTROPIC_EXPORT ExternalBridge {
89public:
98 entropic_handle_t handle,
100 const std::filesystem::path& project_dir);
101
103
109 bool start();
110
115 void stop();
116
123 const std::filesystem::path& socket_path() const { return socket_path_; }
124
137 bool ask_streaming() const { return config_.ask_streaming; }
138
150 const ExternalMCPConfig& config() const { return config_; }
151
167 std::string dispatch(const std::string& request, int client_fd);
168
169 // ── Turn queue (gh#144, v2.12.0) ─────────────────────────
170
201 bool begin_turn_wait(long timeout_ms, long* waited_ms_out,
202 const std::string& session_key = "");
203
210 void end_turn_wait(const std::string& session_key = "");
211
219 void fail_task_engine_busy(const std::string& task_id, long waited_ms);
220
230 void commit_async_final_state(const std::string& task_id,
231 const std::string& status,
232 const std::string& phase,
233 const std::string& text);
234
241 size_t turn_queue_depth() const;
242
249 bool turn_in_flight() const;
250
258 nlohmann::json handle_ask_status(const nlohmann::json& args);
259
281 void attach_phase_observer(const std::string& task_id);
282
288 void detach_phase_observer();
289
306 void run_async_ask(const std::string& prompt,
307 const std::string& task_id,
308 int client_fd,
309 const std::string& session_key = "",
310 const std::string& tier = "");
311
332 void write_sentinel(const std::string& task_id,
333 const std::string& status);
334
350 std::filesystem::path async_sentinel_dir() const;
351
365 void set_async_sentinel_root(const std::filesystem::path& root);
366
372 void cleanup_expired_tasks();
373
375 mutable std::mutex tasks_mutex_;
376
386 struct AsyncTask {
387 std::string status = "queued";
388 std::string phase = "queued";
389 std::string result;
390 std::chrono::steady_clock::time_point created;
391 std::string session_key;
392 int owner_fd = -1;
393 };
394
407 std::unordered_map<std::string, AsyncTask>& tasks_for_cancel() {
408 return tasks_;
409 }
410
417 const std::string& active_task_id_for_observer() const {
418 return active_task_id_;
419 }
420
427 void subscribe(int fd);
428
435 void unsubscribe(int fd);
436
448 void broadcast_notification(const nlohmann::json& notif);
449
463 void notify_client(int fd, const nlohmann::json& notif);
464
471 size_t subscriber_count() const {
472 std::lock_guard<std::mutex> lock(subscribers_mutex_);
473 return subscribers_.size();
474 }
475
489 void update_task_phase(const std::string& task_id,
490 const std::string& status,
491 const std::string& phase);
492
507 return observer_gen_.load() != attached_gen_;
508 }
509
510private:
516 void accept_loop();
517
524 void serve_client(int client_fd);
525
526
537 void reap_finished_clients_locked();
538
546 void reap_finished_async_locked();
547
556 void join_async_workers();
557
558 entropic_handle_t handle_;
559 ExternalMCPConfig config_;
560
561 // ── Turn queue (gh#144, v2.12.0; per-session lanes v2.14.0) ──
562 // Serialises every path that reaches the engine — sync ask, streaming
563 // ask, AND the detached async worker — PER SESSION. Missing the async path would let
564 // an async ask bypass the queue and collide with a sync one, earning an
565 // ALREADY_RUNNING where the caller expected to be queued.
566 //
567 // Nothing else on the bridge may take turn_mutex_. In particular
568 // handle_clear -> cancel_inflight_async_tasks interrupts that SESSION's
569 // run, and must NOT wait behind the turn it is trying to cancel —
570 // that would be gh#109's failure one layer up. It reaches the engine
571 // through entropic_context_clear (api_mutex), which runs never take.
573 struct TurnLane {
574 uint64_t next_ticket = 0;
575 uint64_t now_serving = 0;
576 size_t waiters = 0;
577 bool busy = false;
580 std::set<uint64_t> abandoned;
581 };
582 mutable std::mutex turn_mutex_;
583 std::condition_variable turn_cv_;
584 std::map<std::string, TurnLane> lanes_;
585
593 std::string lane_for(const std::string& session_key) const;
594
605 void async_worker(const std::string& prompt, const std::string& task_id,
606 int client_fd, const std::string& session_key,
607 const std::string& tier);
608 std::filesystem::path socket_path_;
609 std::string bound_canonical_;
610 int listen_fd_ = -1;
611 std::atomic<bool> running_{false};
612 std::thread accept_thread_;
613
622 struct ClientThread {
623 int fd;
627 std::mutex fd_mutex;
628 std::atomic<bool> finished{false};
629 std::thread thread;
630 };
631 std::vector<std::unique_ptr<ClientThread>> client_threads_;
635 struct AsyncWorker {
636 std::atomic<bool> finished{false};
637 std::thread thread;
638 };
639 std::vector<std::unique_ptr<AsyncWorker>> async_workers_;
640 std::mutex async_workers_mutex_;
641 mutable std::mutex client_threads_mutex_;
642
644 std::unordered_map<std::string, AsyncTask> tasks_;
645
651 std::unordered_set<int> subscribers_;
652 mutable std::mutex subscribers_mutex_;
653
659 std::string active_task_id_;
660
665 std::atomic<uint64_t> observer_gen_{0};
666
670 uint64_t attached_gen_ = 0;
671
675 std::filesystem::path async_sentinel_root_override_;
676};
677
678} // namespace entropic
Unix socket MCP bridge for external client access.
std::mutex tasks_mutex_
Async task mutex (public for dispatch_tool access).
const ExternalMCPConfig & config() const
The external MCP config this bridge was constructed with.
const std::string & active_task_id_for_observer() const
Return the currently-active async task_id (if any).
const std::filesystem::path & socket_path() const
Get the socket path (for logging/diagnostics).
bool ask_streaming() const
Whether entropic.ask routes through entropic_run_streaming.
bool observer_call_is_stale() const
Test whether an in-flight observer callback is stale.
std::unordered_map< std::string, AsyncTask > & tasks_for_cancel()
Mutable accessor to the task registry.
size_t subscriber_count() const
Current subscriber count (for diagnostics / testing).
Configuration structs with defaults.
Symbol visibility macro for all exported symbols.
Activate model on GPU (WARM → ACTIVE).
std::string config
–config FILE
Definition serve.cpp:38
Async task state for background entropic.ask runs.
std::string result
Final text or error message.
std::chrono::steady_clock::time_point created
For TTL cleanup.
std::string session_key
Session it runs under (v2.14.0)
External MCP server configuration (Entropic-as-server).
Definition config.h:856
Engine handle struct — owns all subsystems.