NeoGraph 0.16.0
A C++20 Graph Agent Engine Library — LangGraph for C++
Loading...
Searching...
No Matches
tool_execution.h
Go to the documentation of this file.
1
18#pragma once
19
20#include <neograph/api.h>
22#include <neograph/json.h>
23#include <asio/awaitable.hpp>
24
25#include <chrono>
26#include <cstdint>
27#include <memory>
28#include <optional>
29#include <stdexcept>
30#include <string>
31#include <string_view>
32
33namespace neograph {
34class Tool;
35struct ToolExecutionContext;
36
37namespace graph {
38class CancelToken;
39}
40
42enum class ToolExecutionImplementation : std::uint8_t {
49};
50
52enum class ToolTerminalStatus : std::uint8_t {
53 Succeeded,
54 Failed,
55 CancelledBeforeStart,
56 CancellationRequested,
57 Expired,
58 TimedOut,
59 Killed,
60 InputRequired,
61 ReconciliationRequired,
62 Rejected,
63};
64
65NEOGRAPH_API std::string_view to_string(ToolTerminalStatus status) noexcept;
66NEOGRAPH_API ToolTerminalStatus tool_terminal_status_from_string(std::string_view value);
67
72inline bool tool_call_not_started(ToolTerminalStatus status) noexcept {
73 return status == ToolTerminalStatus::CancelledBeforeStart ||
74 status == ToolTerminalStatus::Expired || status == ToolTerminalStatus::Rejected;
75}
76
78struct NEOGRAPH_API ToolExecutionResult {
79 ToolTerminalStatus status = ToolTerminalStatus::Failed;
80 std::string output;
81 std::string error;
82 bool retryable = false;
83 bool effect_uncertain = false;
85 std::optional<int> exit_code;
87 std::optional<int> signal_number;
89 bool output_truncated = false;
93 bool refused = false;
94
95 [[nodiscard]] bool succeeded() const noexcept {
96 return status == ToolTerminalStatus::Succeeded;
97 }
98};
99
109class NEOGRAPH_API ToolExecutionError : public std::runtime_error {
110public:
112 [[nodiscard]] const ToolExecutionResult& result() const noexcept { return result_; }
113 [[nodiscard]] ToolTerminalStatus status() const noexcept { return result_.status; }
114
115private:
116 ToolExecutionResult result_;
117};
118
134class NEOGRAPH_API ToolRequestRefusal {
135public:
136 virtual ~ToolRequestRefusal();
137
140 [[nodiscard]] virtual bool retryable() const noexcept = 0;
141};
142
144class NEOGRAPH_API ToolRequestRejected : public std::runtime_error, public ToolRequestRefusal {
145public:
146 explicit ToolRequestRejected(const std::string& message, bool retryable = false);
147 ~ToolRequestRejected() override;
148 [[nodiscard]] bool retryable() const noexcept override { return retryable_; }
149
150private:
151 bool retryable_;
152};
153
155enum class ToolEffectClass : std::uint8_t {
156 Unknown,
157 ReadOnly,
158 ExternalRead,
159 WorkspaceWrite,
160 ExternalWrite,
161};
162
163enum class ToolIdempotency : std::uint8_t {
164 Unknown,
165 Idempotent,
166 Conditional,
167 NonIdempotent,
168};
169
171enum class ToolConcurrency : std::uint8_t {
173 Reentrant,
177 Exclusive,
179 Capacity,
184};
188 std::string owner_scope;
190 std::string root_run_id;
192 std::string thread_id;
194 std::string request_id;
195};
196
199 std::string program_version_id;
200 std::string operation_id;
201 std::string grant_id;
203 std::uint64_t attempt = 0;
204 std::string binding_fingerprint;
205};
206
216 static constexpr std::uint32_t SCHEMA_VERSION = 2;
218 static constexpr std::size_t DEFAULT_ERROR_LIMIT_BYTES = 2048;
219
220 std::uint32_t schema_version = SCHEMA_VERSION;
221 std::string policy_id;
222 std::string policy_source{"host-default"};
223 std::string policy_hash;
224 ToolExecutionImplementation implementation = ToolExecutionImplementation::NativeAsync;
225 ToolConcurrency concurrency = ToolConcurrency::KeyedExclusive;
226 std::uint32_t capacity = 1;
227 std::uint32_t max_pending = 128;
228 std::chrono::milliseconds queue_timeout{60'000};
229 std::chrono::milliseconds execution_timeout{0};
230 bool cancellable = true;
231 ToolEffectClass effect = ToolEffectClass::Unknown;
232 ToolIdempotency idempotency = ToolIdempotency::Unknown;
233 std::uint32_t retry_max_attempts = 1;
234 std::chrono::milliseconds retry_backoff{0};
235 std::size_t output_limit_bytes = 16U * 1024U * 1024U;
241 std::size_t error_limit_bytes = DEFAULT_ERROR_LIMIT_BYTES;
242 std::string resource_key_template{"tool:{tool}"};
249 std::uint8_t host_priority = 0;
250 std::uint8_t priority_ceiling = 255;
251 std::uint8_t fairness_weight = 1;
252
254 NEOGRAPH_API void validate() const;
255};
256
259 std::string resource_key;
260 std::string owner_scope;
261 std::uint32_t capacity = 1;
262 std::uint32_t max_pending = 128;
263 std::chrono::milliseconds queue_timeout{60'000};
264 std::uint8_t priority = 0;
265 std::uint8_t fairness_weight = 1;
266};
267
268namespace detail {
269class ResourceArbiterImpl;
270
272asio::awaitable<std::string> execute_blocking_tool_async(Tool& tool, json arguments);
275asio::awaitable<std::string> execute_process_tool_async(
276 Tool& tool, json arguments, ToolExecutionContext context,
277 std::size_t output_limit_bytes, std::chrono::milliseconds timeout);
278} // namespace detail
279
281class NEOGRAPH_API ResourceLease final {
282public:
283 ResourceLease() = default;
284 ResourceLease(const ResourceLease&) = delete;
285 ResourceLease& operator=(const ResourceLease&) = delete;
286 ResourceLease(ResourceLease&& other) noexcept;
287 ResourceLease& operator=(ResourceLease&& other) noexcept;
288 ~ResourceLease() noexcept;
289
290 [[nodiscard]] bool held() const noexcept;
291 [[nodiscard]] std::string_view resource_key() const noexcept;
292 void release() noexcept;
293
294private:
295 friend class ResourceArbiter;
296 friend class detail::ResourceArbiterImpl;
297 ResourceLease(std::shared_ptr<detail::ResourceArbiterImpl> impl,
298 std::string resource_key) noexcept;
299
300 std::shared_ptr<detail::ResourceArbiterImpl> impl_;
301 std::string resource_key_;
302};
303
311class NEOGRAPH_API ResourceArbiter final {
312public:
315 ResourceArbiter(const ResourceArbiter&) = delete;
316 ResourceArbiter& operator=(const ResourceArbiter&) = delete;
317
318 asio::awaitable<ResourceLease> acquire_async(
319 ResourceRequest request,
320 std::shared_ptr<graph::CancelToken> cancel_token = {});
321
322private:
323 std::shared_ptr<detail::ResourceArbiterImpl> impl_;
324};
325
327class NEOGRAPH_API ToolExecutionPolicyRegistry final {
328public:
333
335 void upsert(std::string tool_name, ToolExecutionPolicy policy);
336 void erase(std::string_view tool_name);
337 void set_default(ToolExecutionPolicy policy);
338 [[nodiscard]] ToolExecutionPolicy resolve(std::string_view tool_name) const;
339
340private:
341 class Impl;
342 std::unique_ptr<Impl> impl_;
343};
344
345struct ToolExecutionControllerConfig {
346 std::shared_ptr<ToolExecutionPolicyRegistry> policies;
347 std::shared_ptr<ResourceArbiter> arbiter;
353 std::shared_ptr<HostAdmissionController> host_admission;
354};
355
363class NEOGRAPH_API ToolExecutionController final {
364public:
365 explicit ToolExecutionController(ToolExecutionControllerConfig config = {});
368 ToolExecutionController& operator=(const ToolExecutionController&) = delete;
369
383 asio::awaitable<ToolExecutionResult> execute_result_async(
384 Tool& tool, json arguments, ToolExecutionContext context);
385
394 asio::awaitable<std::string> execute_async(Tool& tool,
395 json arguments,
396 ToolExecutionContext context);
397
398 [[nodiscard]] std::shared_ptr<ToolExecutionPolicyRegistry> policies() const;
399 [[nodiscard]] std::shared_ptr<ResourceArbiter> arbiter() const;
400 [[nodiscard]] std::shared_ptr<HostAdmissionController> host_admission() const;
401
402private:
403 friend class NativeHookExecutionAdapter;
407 asio::awaitable<ToolExecutionResult> execute_hook_target_async(
408 Tool& tool, json arguments, ToolExecutionContext context);
409 asio::awaitable<ToolExecutionResult> execute_result_async(
410 Tool& tool, json arguments, ToolExecutionContext context, bool retry_after_timeout);
411
412 class Impl;
413 std::unique_ptr<Impl> impl_;
414};
415
417NEOGRAPH_API std::shared_ptr<ToolExecutionController> default_tool_execution_controller();
418
419} // namespace neograph
NEOGRAPH_API export/import macro for shared-library builds.
Native-only execution bridge.
Definition hook.h:175
Thread-safe FIFO resource arbiter.
RAII proof that the caller owns one ResourceArbiter capacity slot.
The single tool execution boundary shared by Agent and graph dispatch.
asio::awaitable< std::string > execute_async(Tool &tool, json arguments, ToolExecutionContext context)
Throwing facade over execute_result_async: the output on success, graph::CancelledException for Cance...
asio::awaitable< ToolExecutionResult > execute_result_async(Tool &tool, json arguments, ToolExecutionContext context)
Typed terminal contract shared by all tool implementations.
Thrown by the throwing facade ToolExecutionController::execute_async for every terminal status except...
Thread-safe named policy registry owned by an application host.
void upsert(std::string tool_name, ToolExecutionPolicy policy)
Replaces the policy for one exact tool name after validating it.
What an exception says when the call it ended is known not to have run: the service that hosts the to...
virtual bool retryable() const noexcept=0
Whether sending the same request again may succeed.
Thrown by a Tool that knows its call did not run (see ToolRequestRefusal).
bool retryable() const noexcept override
Whether sending the same request again may succeed.
Abstract base class for tools that agents can call.
Definition tool.h:114
Bounded host-resource admission for Program and external work.
Thin C++ RAII wrapper around yyjson with nlohmann-compatible API.
Vector of independently reserved host resources.
One admission request to the fair resource arbiter.
Exact host authorization for one invocation of mediated Tool dispatch.
Per-invocation context for a cooperative asynchronous tool.
Definition tool.h:39
Stable request identity used for resource accounting and audit correlation.
std::string root_run_id
Durable root Program run identity, when the call originated in Program.
std::string request_id
Host-generated request/correlation identifier, when available.
std::string thread_id
Current graph thread identity, when available.
std::string owner_scope
Stable caller or tenant scope. Empty is normalized to "anonymous".
Host-owned policy for a named tool.
NEOGRAPH_API void validate() const
Throws std::invalid_argument for a malformed or unsafe policy.
HostResourceVector host_resources
Host-wide reservation held while the tool implementation executes.
One stable result contract for native async and blocking Tool bridges.
std::optional< int > exit_code
Exit status of a completed process bridge, when a child was launched.
std::optional< int > signal_number
Signal that terminated a process bridge, when applicable.
ToolTerminalStatus
Terminal state returned by the typed tool-execution surface.
bool tool_call_not_started(ToolTerminalStatus status) noexcept
The statuses of a call that never started: the run was cancelled first, its deadline or queue wait ra...
ToolExecutionImplementation
How an admitted invocation enters a Tool implementation.
@ BlockingProcess
Launch a ProcessTool in its own process group on the blocking bridge.
@ BlockingThread
Invoke Tool::execute on the bounded blocking executor explicitly.
@ NativeAsync
Call Tool::execute_async. The base implementation offloads sync work.
ToolEffectClass
Explicit effect classification used to decide retry and recovery behavior.
ToolConcurrency
Scheduler concurrency semantics for one policy-derived resource key.
@ SingleFlight
One in-flight invocation per key; concurrent callers share its result.
@ ExternalLimited
The external service owns concurrency; enforce the declared local capacity.
@ Exclusive
One process-wide invocation regardless of argument-derived aliases.
@ Capacity
Up to capacity active invocations for each derived resource key.
@ Reentrant
No arbiter lease. Only safe for a host-declared re-entrant tool.
@ KeyedExclusive
One active invocation for each derived resource key.
NEOGRAPH_API std::shared_ptr< ToolExecutionController > default_tool_execution_controller()
Process-default conservative controller for legacy callers.
asio::awaitable< std::string > execute_process_tool_async(Tool &tool, json arguments, ToolExecutionContext context, std::size_t output_limit_bytes, std::chrono::milliseconds timeout)
ProcessTool bridge. It returns stdout/stderr and throws a typed bridge exception for timeout,...
asio::awaitable< std::string > execute_blocking_tool_async(Tool &tool, json arguments)
Shared bounded executor used only by synchronous Tool fallbacks.