Entropic 2.14.3
Local-first agentic inference engine
Loading...
Searching...
No Matches
entropic::ExternalBridge Class Reference

Unix socket MCP bridge for external client access. More...

#include <entropic/mcp/external_bridge.h>

Classes

struct  AsyncTask
 Async task state for background entropic.ask runs. More...
 

Public Member Functions

 ExternalBridge (entropic_handle_t handle, const ExternalMCPConfig &config, const std::filesystem::path &project_dir)
 Construct with engine handle and config.
 
 ~ExternalBridge ()
 Destructor — stop if running.
 
bool start ()
 Start the background accept loop.
 
void stop ()
 Stop the accept loop and close the socket.
 
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.
 
const ExternalMCPConfig & config () const
 The external MCP config this bridge was constructed with.
 
std::string dispatch (const std::string &request, int client_fd)
 Dispatch one JSON-RPC request and return the response.
 
bool begin_turn_wait (long timeout_ms, long *waited_ms_out, const std::string &session_key="")
 Take a ticket and wait for this caller's turn on the engine.
 
void end_turn_wait (const std::string &session_key="")
 Hand the turn to the next ticket in this session's lane.
 
void fail_task_engine_busy (const std::string &task_id, long waited_ms)
 Mark an async task failed because its turn never came (gh#144).
 
void commit_async_final_state (const std::string &task_id, const std::string &status, const std::string &phase, const std::string &text)
 Record an async task's terminal state and write its sentinel.
 
size_t turn_queue_depth () const
 How many callers are waiting behind the one(s) in flight.
 
bool turn_in_flight () const
 Whether a turn is currently running on the engine.
 
nlohmann::json handle_ask_status (const nlohmann::json &args)
 Handle entropic.ask_status — check async task state.
 
void attach_phase_observer (const std::string &task_id)
 Run an async entropic.ask in a background thread.
 
void detach_phase_observer ()
 Clear the phase observer installed by attach_phase_observer.
 
void run_async_ask (const std::string &prompt, const std::string &task_id, int client_fd, const std::string &session_key="", const std::string &tier="")
 Run an ask on a background worker and notify its owner.
 
void write_sentinel (const std::string &task_id, const std::string &status)
 Write the sentinel file for an async task completion.
 
std::filesystem::path async_sentinel_dir () const
 Sentinel directory (lazy: returns empty path until the engine's log_dir is configured).
 
void set_async_sentinel_root (const std::filesystem::path &root)
 Override the async sentinel root directory.
 
void cleanup_expired_tasks ()
 Remove tasks older than TTL from the registry.
 
std::unordered_map< std::string, AsyncTask > & tasks_for_cancel ()
 Mutable accessor to the task registry.
 
const std::string & active_task_id_for_observer () const
 Return the currently-active async task_id (if any).
 
void subscribe (int fd)
 Add a connected fd to the subscriber set.
 
void unsubscribe (int fd)
 Remove an fd from the subscriber set.
 
void broadcast_notification (const nlohmann::json &notif)
 Write a JSON-RPC notification to every subscribed fd.
 
void notify_client (int fd, const nlohmann::json &notif)
 Write a notification to ONE subscribed client (v2.14.0).
 
size_t subscriber_count () const
 Current subscriber count (for diagnostics / testing).
 
void update_task_phase (const std::string &task_id, const std::string &status, const std::string &phase)
 Update status/phase for a tracked task atomically.
 
bool observer_call_is_stale () const
 Test whether an in-flight observer callback is stale.
 

Public Attributes

std::mutex tasks_mutex_
 Async task mutex (public for dispatch_tool access).
 

Detailed Description

Unix socket MCP bridge for external client access.

Listens on a unix domain socket and dispatches JSON-RPC requests to the engine handle it was constructed with. Lifetime is tied to the engine handle — created in configure_common, destroyed before engine teardown.

Issue #12 (v2.1.4): also writes per-task sentinel files at <log_dir>/async/<task_id>.{done,failed,cancelled} so external monitors can use inotify rather than parsing log output.

gh#145 (v2.12.0): exposes config() so the tool-name handlers can read the consumer's configured namespace. gh#144 (v2.12.0): owns the FIFO turn queue that serialises every caller reaching the engine. v2.14.2: owns its async workers (joined by stop()) instead of detaching them.

@dg_internal

Version
2.14.2

Definition at line 88 of file external_bridge.h.

Constructor & Destructor Documentation

◆ ExternalBridge()

entropic::ExternalBridge::ExternalBridge ( entropic_handle_t  handle,
const ExternalMCPConfig &  config,
const std::filesystem::path &  project_dir 
)

Construct with engine handle and config.

Parameters
handleEngine handle (must outlive the bridge).
configExternal MCP configuration.
project_dirProject directory (for socket path derivation).
Version
2.12.0
Parameters
handleEngine handle (must outlive the bridge).
configExternal MCP configuration.
project_dirProject directory (for socket path derivation). @req REQ-BRIDGE-001
Version
2.0.8

Definition at line 1288 of file external_bridge.cpp.

◆ ~ExternalBridge()

entropic::ExternalBridge::~ExternalBridge ( )

Destructor — stop if running.

@req REQ-BRIDGE-001

Version
2.0.8

Definition at line 1303 of file external_bridge.cpp.

Member Function Documentation

◆ active_task_id_for_observer()

const std::string & entropic::ExternalBridge::active_task_id_for_observer ( ) const
inline

Return the currently-active async task_id (if any).

Returns
Task id or empty string. Caller MUST hold tasks_mutex_. @utility
Version
2.0.6-rc16.2

Definition at line 417 of file external_bridge.h.

◆ ask_streaming()

bool entropic::ExternalBridge::ask_streaming ( ) const
inline

Whether entropic.ask routes through entropic_run_streaming.

When false, handle_ask uses entropic_run (no on_token callback), which is compatible with MTP and avoids the streaming-path 316 throw (gh#115, v2.9.12). Static-dispatch helper for file-scope functions that hold ExternalBridge*.

Returns
config_.ask_streaming (default true). @utility
Version
2.9.12

Definition at line 137 of file external_bridge.h.

◆ async_sentinel_dir()

std::filesystem::path entropic::ExternalBridge::async_sentinel_dir ( ) const

Sentinel directory (lazy: returns empty path until the engine's log_dir is configured).

Resolve the async sentinel directory.

Issue #12 (v2.1.4). Lives at <log_dir>/async/ by default (or <override>/async/ if set_async_sentinel_root was called). The bridge does not own log_dir lifetime — the path is recomputed each call so working-dir swaps via configure_dir take effect on the next async task.

Returns
Absolute sentinel directory path. Empty if neither log_dir nor an override is configured. @utility
Version
2.1.4

Override (set via set_async_sentinel_root) takes precedence; falls back to <handle_->config.log_dir>/async. Returns an empty path if neither source is configured. Issue #12 (v2.1.4).

Returns
Sentinel directory path, or an empty path when neither an override nor a configured log_dir is available. @req REQ-BRIDGE-001
Version
2.1.4

Definition at line 2243 of file external_bridge.cpp.

◆ attach_phase_observer()

void entropic::ExternalBridge::attach_phase_observer ( const std::string &  task_id)

Run an async entropic.ask in a background thread.

Install phase observer scoped to one task.

Parameters
promptUser prompt.
task_idAssigned task ID.
client_fdSocket fd for completion notification. @dg_internal
Version
2.0.11

Install VERIFYING-state observer scoped to one task.

Registers a state-change observer on the handle and records task_id as the active task. The observer maps VERIFYING transitions onto phase="validating" (first) or "revising" (subsequent). Pair with detach_phase_observer(). (P1-5 follow-up, 2.0.6-rc16.2)

Parameters
task_idTask whose phase will be updated. @dg_internal
Version
2.0.6-rc16.2

Increments observer_gen_ under tasks_mutex_ and captures it as attached_gen_ so phase_observer_cb can detect stale post-detach fires via a simple generation comparison. (E5+E6, 2.1.0)

Parameters
task_idTask whose phase transitions the observer tracks. @req REQ-BRIDGE-001
Version
2.1.0

Definition at line 1822 of file external_bridge.cpp.

◆ begin_turn_wait()

bool entropic::ExternalBridge::begin_turn_wait ( long  timeout_ms,
long *  waited_ms_out,
const std::string &  session_key = "" 
)

Take a ticket and wait for this caller's turn on the engine.

The engine admits one turn at a time and refuses the loser with ENTROPIC_ERROR_ALREADY_RUNNING (gh#144 stage 1). A refusal is the right contract for a direct C API consumer, but the wrong one for a shared host: several MCP clients on one engine want to QUEUE, which is the whole point of one resident model. This serialises them here so the engine's refusal is never reached from the bridge.

FIFO by ticket rather than a bare mutex: a mutex gives no fairness guarantee (a caller can starve), no observable depth, and no way to bound the wait — and MCP clients have their own timeouts, so blocking one indefinitely is worse than telling it the truth.

Parameters
timeout_msGive up after this long and let the caller report a busy engine rather than hang.
waited_ms_outOptional; receives the time actually spent waiting.
session_keySession this turn runs under (v2.14.0). Callers of the SAME session queue FIFO; callers of different sessions wait only for their own lane, because the engine runs sessions concurrently (gh#158). When the handle has concurrent_sessions: false every session shares one lane, as before — the engine would refuse a second concurrent session.
Returns
true when it is this caller's turn — the caller MUST then call end_turn_wait() with the same session_key. false on timeout, owning nothing. @req REQ-BRIDGE-001
Version
2.14.0
Parameters
timeout_msGive up after this long rather than hang the client.
waited_ms_outOptional; receives time actually spent waiting.
Returns
true when it is this caller's turn (caller must end_turn_wait()). @req REQ-BRIDGE-001
Version
2.12.0

Definition at line 1180 of file external_bridge.cpp.

◆ broadcast_notification()

void entropic::ExternalBridge::broadcast_notification ( const nlohmann::json &  notif)

Write a JSON-RPC notification to every subscribed fd.

Broadcast a JSON-RPC notification to every subscriber.

Writes are serialized under subscribers_mutex_ so concurrent broadcasts do not interleave on the same fd. Fds that fail to write are removed from the set.

Parameters
notifJSON-RPC notification object. @dg_internal (public for unit test access)
Version
2.0.6-rc16

Issue #4 (v2.1.2, part C): write is non-blocking via send(MSG_DONTWAIT). A subscriber whose recv buffer is full (slow / non-draining consumer) returns EAGAIN/EWOULDBLOCK and is dropped on the same path as EBADF/EPIPE, as is a partial write. Pre-2.1.2 the broadcast used blocking write(), which let one stalled consumer wedge the async-task thread (issue #4). The longer-term replacement is a per-subscriber outbound queue + writer thread (proposal P2-20260429-001).

v2.14.2: the sends happen under subscribers_mutex_. The old snapshot- then-send let a subscriber unsubscribe and close mid-broadcast, and a reused descriptor then received another client's notification (TSan). Non-blocking sends make holding the lock cheap, which is why the snapshot was never needed to avoid stalls.

Parameters
notifJSON-RPC notification object. @req REQ-BRIDGE-001
Version
2.14.2

Definition at line 2171 of file external_bridge.cpp.

◆ cleanup_expired_tasks()

void entropic::ExternalBridge::cleanup_expired_tasks ( )

Remove tasks older than TTL from the registry.

Remove tasks older than 15 minutes from the registry, AND delete their sentinel files if present.

@dg_internal

Version
2.0.11

Issue #12 (v2.1.4): sentinel files would otherwise accumulate in <log_dir>/async/ indefinitely. Cleanup uses the same TTL as the in-memory registry so an external monitor that consumed the sentinel within 15 minutes still sees a consistent picture.

@req REQ-BRIDGE-001

Version
2.1.4

Definition at line 2209 of file external_bridge.cpp.

◆ commit_async_final_state()

void entropic::ExternalBridge::commit_async_final_state ( const std::string &  task_id,
const std::string &  status,
const std::string &  phase,
const std::string &  text 
)

Record an async task's terminal state and write its sentinel.

Parameters
task_idTask reaching a terminal state.
statusTerminal status string.
phaseTerminal phase string.
textResult or error text. @utility
Version
2.12.0

Extracted from run_async_ask (gh#144, v2.12.0) to keep it inside the SLOC and ABC gates once the turn-queue wait was added.

Issue #12 (v2.1.4): the sentinel is written UNDER tasks_mutex_, before the MCP notification fires, so an external monitor reacting to the sentinel can immediately call the ask_status tool and see the same terminal state.

Parameters
task_idTask reaching a terminal state.
statusTerminal status string.
phaseTerminal phase string.
textResult or error text. @dg_internal
Version
2.12.0

Definition at line 1869 of file external_bridge.cpp.

◆ config()

const ExternalMCPConfig & entropic::ExternalBridge::config ( ) const
inline

The external MCP config this bridge was constructed with.

gh#145 (v2.12.0): file-scope handlers holding an ExternalBridge* need the configured tool_prefix and description overrides to build and resolve tool names. Same static-dispatch rationale as ask_streaming().

Returns
Const reference to the config snapshot. @utility
Version
2.12.0

Definition at line 150 of file external_bridge.h.

◆ detach_phase_observer()

void entropic::ExternalBridge::detach_phase_observer ( )

Clear the phase observer installed by attach_phase_observer.

Clear phase observer and the active-task pointer.

@dg_internal

Version
2.0.6-rc16.2

Increments observer_gen_ under the lock before clearing active_task_id_ — any in-flight phase_observer_cb will see a generation mismatch and return immediately without accessing stale state. entropic_set_state_observer(nullptr) is called after the lock is released. (E5+E6, 2.1.0)

@req REQ-BRIDGE-001

Version
2.1.0

Definition at line 1843 of file external_bridge.cpp.

◆ dispatch()

std::string entropic::ExternalBridge::dispatch ( const std::string &  request,
int  client_fd 
)

Dispatch one JSON-RPC request and return the response.

Dispatch a JSON-RPC request and return the response.

Public since gh#144 (v2.12.0) for the same reason handle_ask_status is: this is the bridge's protocol entry point, and the tool schemas it serves are otherwise observable only over a real unix socket. The class is @dg_internal and crosses no public ABI, so exposing it costs nothing a consumer can depend on.

Parameters
requestRaw JSON-RPC request line.
client_fdSocket fd for streaming progress (-1 when none).
Returns
JSON-RPC response string, or empty for a notification. @req REQ-BRIDGE-001
Version
2.12.0

Per JSON-RPC 2.0 spec, messages without an "id" field are notifications and MUST NOT receive a response. Returns empty string for notifications so the caller knows not to write back.

Parameters
requestRaw JSON-RPC request string.
client_fdSocket fd for streaming progress (entropic.ask).
Returns
JSON-RPC response string, or empty for notifications. @req REQ-BRIDGE-001
Version
2.12.0

Definition at line 1761 of file external_bridge.cpp.

◆ end_turn_wait()

void entropic::ExternalBridge::end_turn_wait ( const std::string &  session_key = "")

Hand the turn to the next ticket in this session's lane.

Hand the turn to the next ticket in line.

Parameters
session_keyThe key passed to the matching begin_turn_wait(). @req REQ-BRIDGE-001
Version
2.14.0

@req REQ-BRIDGE-001

Version
2.12.0

Definition at line 1218 of file external_bridge.cpp.

◆ fail_task_engine_busy()

void entropic::ExternalBridge::fail_task_engine_busy ( const std::string &  task_id,
long  waited_ms 
)

Mark an async task failed because its turn never came (gh#144).

Parameters
task_idTask that gave up waiting.
waited_msHow long it waited before giving up. @utility
Version
2.12.0

The task stays "queued" until the ticket is actually held, so ask_status reports the truth rather than claiming to run while blocked. On timeout it becomes a terminal error with the wait recorded, and the sentinel is written under tasks_mutex_ like every other terminal transition.

Parameters
task_idTask that gave up waiting.
waited_msHow long it waited before giving up. @dg_internal
Version
2.12.0

Definition at line 1895 of file external_bridge.cpp.

◆ handle_ask_status()

json entropic::ExternalBridge::handle_ask_status ( const nlohmann::json &  args)

Handle entropic.ask_status — check async task state.

Parameters
argsTool arguments (JSON with task_id).
Returns
MCP tool result JSON. @dg_internal
Version
2.0.11

Returns coarse status + granular phase so pollers see progression through queued → running → running:<tier> → done|failed|cancelled.

Parameters
argsTool arguments (must contain "task_id").
Returns
MCP tool result JSON with status/phase/result/error. @req REQ-BRIDGE-001
Version
2.0.6-rc16

Definition at line 762 of file external_bridge.cpp.

◆ notify_client()

void entropic::ExternalBridge::notify_client ( int  fd,
const nlohmann::json &  notif 
)

Write a notification to ONE subscribed client (v2.14.0).

For anything that belongs to a single caller — an async task's completion above all. Nothing is sent when fd is negative or is not (or no longer) a subscriber; a failed write drops the subscriber, as broadcast_notification() does.

Parameters
fdOwning client's fd.
notifJSON-RPC notification object. @dg_internal (public for unit test access)
Version
2.14.0

v2.14.2: the membership check and the send happen under ONE hold of subscribers_mutex_. They were split, so a client could unsubscribe and close between them — and once the descriptor number was reused, the notification went to a different client (TSan; same class as gh#208). The send is non-blocking, so holding the lock across it stalls nobody; a client's thread unsubscribes before it closes, so a sender holding the lock never sees a closed or reused fd.

Parameters
fdOwning client's fd; negative = nobody.
notifJSON-RPC notification object. @dg_internal
Version
2.14.2

Definition at line 2141 of file external_bridge.cpp.

◆ observer_call_is_stale()

bool entropic::ExternalBridge::observer_call_is_stale ( ) const
inline

Test whether an in-flight observer callback is stale.

Called from phase_observer_cb (free function) under tasks_mutex_. Returns true if observer_gen_ has been bumped since this callback's attach point — indicating detach_phase_observer (or a re-attach for a different task) has run and the firing callback should exit without touching state. (E5+E6, 2.1.0)

Returns
true if the firing observer call should be discarded. @utility
Version
2.1.0

Definition at line 506 of file external_bridge.h.

◆ run_async_ask()

void entropic::ExternalBridge::run_async_ask ( const std::string &  prompt,
const std::string &  task_id,
int  client_fd,
const std::string &  session_key = "",
const std::string &  tier = "" 
)

Run an ask on a background worker and notify its owner.

Run an async entropic.ask in a detached background thread.

v2.14.0: the completion notification goes to the client that started the task (client_fd) and to no other — it used to be broadcast, so every connected client learned every other client's task ids and outcomes. tier locks the turn exactly as entropic_run_session_as().

Parameters
promptUser prompt.
task_idAssigned task id.
client_fdOwning client; -1 for none (nobody is notified).
session_keySession to run under ("" = default).
tierTier or identity to lock to ("" = route as usual). @req REQ-BRIDGE-001
Version
2.14.0

Creates a task registry entry, runs the engine, stores the result, writes the per-task sentinel file (#12, v2.1.4), and broadcasts notifications/progress to subscribed clients.

Parameters
promptUser prompt.
task_idAssigned task ID.
client_fdSocket fd for completion notification. @req REQ-BRIDGE-001
Version
2.14.2

Definition at line 1952 of file external_bridge.cpp.

◆ set_async_sentinel_root()

void entropic::ExternalBridge::set_async_sentinel_root ( const std::filesystem::path &  root)

Override the async sentinel root directory.

Issue #12 (v2.1.4): primarily a testability hook so unit tests can exercise the sentinel write path without setting up a full engine handle. Production code receives the path from the engine handle's log_dir; passing an empty path here clears the override and restores log_dir resolution.

Parameters
rootNew sentinel root (the bridge appends /async). @utility
Version
2.1.4

Issue #12 (v2.1.4). @req REQ-BRIDGE-001

Version
2.1.4

Definition at line 2256 of file external_bridge.cpp.

◆ socket_path()

const std::filesystem::path & entropic::ExternalBridge::socket_path ( ) const
inline

Get the socket path (for logging/diagnostics).

Returns
Socket path. @utility
Version
2.0.8

Definition at line 123 of file external_bridge.h.

◆ start()

bool entropic::ExternalBridge::start ( )

Start the background accept loop.

Returns
true if the socket was created and listening.
Version
2.0.8
Returns
true if the socket was created and listening. @req REQ-BRIDGE-001
Version
2.14.0

Definition at line 1448 of file external_bridge.cpp.

◆ stop()

void entropic::ExternalBridge::stop ( )

Stop the accept loop and close the socket.

Stop the accept loop, drain client threads, and close the socket.

Version
2.0.8

Issue #4 (v2.1.2, part D): pre-2.1.2 stop() only joined the accept thread because serve_client ran inline. Now each connected client has its own thread blocking in read(); we wake them via shutdown(SHUT_RDWR) on the client fd (which causes the pending read to return EOF) and then join. Order matters:

  1. running_ = false (accept_loop will exit on next poll cycle)
  2. close(listen_fd_) (stops new connections; in-flight clients are unaffected)
  3. join accept_thread_ (no more clients spawn after this)
  4. shutdown each client fd to wake blocking read()s
  5. join each client thread
  6. close client fds (the threads' RAII guards do this too, but we run it again as a defensive measure on shutdown errors)

@req REQ-BRIDGE-001

Version
2.14.2

Definition at line 1524 of file external_bridge.cpp.

◆ subscribe()

void entropic::ExternalBridge::subscribe ( int  fd)

Add a connected fd to the subscriber set.

Remove tasks older than 15 minutes from the registry.

Parameters
fdConnected client socket fd. @dg_internal (public for unit test access)
Version
2.0.6-rc16

@dg_internal

Version
2.0.11

Add a connected fd to the subscriber set.

Parameters
fdClient socket fd. @req REQ-BRIDGE-001
Version
2.0.6-rc16

Definition at line 2083 of file external_bridge.cpp.

◆ subscriber_count()

size_t entropic::ExternalBridge::subscriber_count ( ) const
inline

Current subscriber count (for diagnostics / testing).

Returns
Number of connected clients currently subscribed. @utility
Version
2.0.6-rc16

Definition at line 471 of file external_bridge.h.

◆ tasks_for_cancel()

std::unordered_map< std::string, AsyncTask > & entropic::ExternalBridge::tasks_for_cancel ( )
inline

Mutable accessor to the task registry.

Caller MUST hold tasks_mutex_. Used by the cancel-on-clear path to flip task statuses atomically with the mutex held. Contract verified against all call sites for v2.1.0 (E8). (P1-8, 2.0.6-rc16)

Returns
Reference to task registry. @utility
Version
2.1.0

Definition at line 407 of file external_bridge.h.

◆ turn_in_flight()

bool entropic::ExternalBridge::turn_in_flight ( ) const

Whether a turn is currently running on the engine.

Whether any lane holds a turn (v2.14.0).

Returns
true when any session's lane holds a turn (v2.14.0). @utility
Version
2.14.0
Returns
true while some session's turn runs. @utility
Version
2.14.0

Definition at line 1270 of file external_bridge.cpp.

◆ turn_queue_depth()

size_t entropic::ExternalBridge::turn_queue_depth ( ) const

How many callers are waiting behind the one(s) in flight.

Waiters summed over every lane (v2.14.0).

Returns
Waiters summed over every session's lane (v2.14.0). @utility
Version
2.14.0
Returns
Callers waiting behind a held turn. @utility
Version
2.14.0

Definition at line 1257 of file external_bridge.cpp.

◆ unsubscribe()

void entropic::ExternalBridge::unsubscribe ( int  fd)

Remove an fd from the subscriber set.

Parameters
fdClient socket fd being closed. @dg_internal (public for unit test access)
Version
2.0.6-rc16
Parameters
fdClient socket fd being closed. @req REQ-BRIDGE-001
Version
2.0.6-rc16

Definition at line 2094 of file external_bridge.cpp.

◆ update_task_phase()

void entropic::ExternalBridge::update_task_phase ( const std::string &  task_id,
const std::string &  status,
const std::string &  phase 
)

Update status/phase for a tracked task atomically.

Update the status/phase of a tracked task.

Used by the async run thread to advance through queued → running → running:<tier> → done|failed|cancelled. Safe to call with an unknown task_id (no-op).

Parameters
task_idTask identifier.
statusNew coarse status string.
phaseNew granular phase string. @utility
Version
2.0.6-rc16
Parameters
task_idTask identifier.
statusNew coarse status string.
phaseNew granular phase string. @req REQ-BRIDGE-001
Version
2.0.6-rc16

Definition at line 2187 of file external_bridge.cpp.

◆ write_sentinel()

void entropic::ExternalBridge::write_sentinel ( const std::string &  task_id,
const std::string &  status 
)

Write the sentinel file for an async task completion.

Issue #12 (v2.1.4): persistent on-disk completion signal so external monitors (CLI scripts, watchdogs) can inotifywait on the async sentinel directory without parsing log output or subscribing to MCP notifications. Sentinel filename encodes the terminal status: <task_id>.done, .failed, .cancelled.

Caller MUST hold tasks_mutex_. The sentinel write happens under-lock together with the status update so that any external monitor reacting to the sentinel sees a consistent registry state on a follow-up entropic.ask_status query.

Parameters
task_idTask identifier.
statusTerminal status string (done | error | cancelled). error maps to .failed. @utility
Version
2.1.4

Issue #12 (v2.1.4). Caller MUST hold tasks_mutex_ — this is invoked inside run_async_ask's terminal critical section so external monitors observe a consistent sentinel + registry pair.

Failure to create the parent directory or open the file is logged at WARN and swallowed; the MCP notification path remains the primary signal and a missing sentinel is non-fatal for callers that don't depend on it.

@req REQ-BRIDGE-001

Version
2.1.4

Definition at line 2292 of file external_bridge.cpp.

Member Data Documentation

◆ tasks_mutex_

std::mutex entropic::ExternalBridge::tasks_mutex_
mutable

Async task mutex (public for dispatch_tool access).

Definition at line 375 of file external_bridge.h.


The documentation for this class was generated from the following files: