NeoGraph 0.16.0
A C++20 Graph Agent Engine Library — LangGraph for C++
Loading...
Searching...
No Matches
hook_outbox.h
Go to the documentation of this file.
1
2#pragma once
3
4#include <neograph/context_store.h>
5#include <neograph/hook.h>
6
7#include <chrono>
8#include <cstdint>
9#include <functional>
10#include <memory>
11#include <optional>
12#include <stdexcept>
13#include <string>
14#include <vector>
15
16namespace neograph {
17
18enum class HookExecutionState : std::uint8_t {
19 Triggered, Pending, Dispatched, Succeeded, Failed, TimedOut,
20 ReconciliationRequired, Cancelled,
21};
22NEOGRAPH_API std::string_view to_string(HookExecutionState value) noexcept;
23
26 std::string receipt_id;
27 bool outcome_known = false;
28 bool succeeded = false;
29 std::string detail;
30};
31
32struct HookExecutionReceiptData {
33 std::string invocation_id;
34 std::uint32_t attempt = 0;
35 HookExecutionState state = HookExecutionState::Failed;
36 ExternalEffectReceipt external_effect;
37 std::string error;
38};
39
41class NEOGRAPH_API HookExecutionReceipt final {
42public:
43 static constexpr std::uint32_t SCHEMA_VERSION = 1;
44 static HookExecutionReceipt create(HookExecutionReceiptData data);
45 static HookExecutionReceipt parse(std::string_view stored_bytes);
46 [[nodiscard]] const std::string& id() const noexcept { return id_; }
47 [[nodiscard]] const HookExecutionReceiptData& data() const noexcept { return data_; }
48 [[nodiscard]] std::string serialize_canonical() const;
49private:
50 explicit HookExecutionReceipt(HookExecutionReceiptData data) : data_(std::move(data)) {}
51 HookExecutionReceiptData data_;
52 std::string id_;
53};
54
55struct HookOutboxEntryData {
56 HookInvocation invocation;
57 RuntimeEvent event;
58 HookExecutionState state = HookExecutionState::Triggered;
59 std::uint32_t attempt_count = 0;
60 std::uint32_t max_attempts = 1;
61 std::chrono::system_clock::time_point deadline{};
62 std::uint64_t fencing_token = 0;
63 std::chrono::system_clock::time_point lease_expires_at{};
64 std::optional<HookExecutionReceipt> receipt;
65};
66
68class NEOGRAPH_API HookOutboxEntry final {
69public:
70 static constexpr std::uint32_t SCHEMA_VERSION = 1;
71 static HookOutboxEntry create(HookOutboxEntryData data);
72 static HookOutboxEntry parse(std::string_view stored_bytes);
73 [[nodiscard]] const std::string& id() const noexcept { return id_; }
74 [[nodiscard]] const HookOutboxEntryData& data() const noexcept { return data_; }
75 [[nodiscard]] std::string serialize_canonical() const;
76private:
77 explicit HookOutboxEntry(HookOutboxEntryData data) : data_(std::move(data)) {}
78 HookOutboxEntryData data_;
79 std::string id_;
80};
81
82struct HookLease { HookOutboxEntry entry; std::uint64_t fencing_token = 0; };
83
85class NEOGRAPH_API HookBoundaryBlocked final : public std::runtime_error {
86public:
87 HookBoundaryBlocked(std::string invocation_id, HookExecutionState state, std::string message);
88 [[nodiscard]] const std::string& invocation_id() const noexcept { return invocation_id_; }
89 [[nodiscard]] HookExecutionState state() const noexcept { return state_; }
90private:
91 std::string invocation_id_;
92 HookExecutionState state_;
93};
94
99class NEOGRAPH_API HookJournal {
100public:
101 virtual ~HookJournal() = default;
102 virtual HookOutboxEntry enqueue(HookInvocation invocation, RuntimeEvent event,
103 std::uint32_t max_attempts,
104 std::chrono::system_clock::time_point deadline) = 0;
105 virtual HookOutboxEntry publish(std::string_view invocation_id) = 0;
106 virtual std::optional<HookLease> claim(std::string_view invocation_id, std::string_view worker_id,
107 std::chrono::system_clock::time_point now,
108 std::chrono::milliseconds lease_duration) = 0;
109 virtual std::optional<HookOutboxEntry> settle(std::string_view invocation_id,
110 std::uint64_t fencing_token,
111 HookExecutionReceipt receipt,
112 std::chrono::system_clock::time_point now) = 0;
113 virtual std::optional<HookOutboxEntry> reconcile(std::string_view invocation_id,
114 HookExecutionState expected_state,
115 std::string_view expected_head_id,
116 std::uint64_t expected_fencing_token,
117 HookExecutionReceipt known_outcome) = 0;
118 virtual std::vector<HookOutboxEntry> pending() const = 0;
119 virtual std::vector<HookOutboxEntry> reconciliation_required() const = 0;
120 virtual std::optional<HookOutboxEntry> get(std::string_view invocation_id) const = 0;
121};
122
123class NEOGRAPH_API InMemoryHookJournal final : public HookJournal {
124public:
125 InMemoryHookJournal();
126 ~InMemoryHookJournal() override;
127 HookOutboxEntry enqueue(HookInvocation, RuntimeEvent, std::uint32_t,
128 std::chrono::system_clock::time_point) override;
129 HookOutboxEntry publish(std::string_view) override;
130 std::optional<HookLease> claim(std::string_view, std::string_view, std::chrono::system_clock::time_point,
131 std::chrono::milliseconds) override;
132 std::optional<HookOutboxEntry> settle(std::string_view, std::uint64_t,
134 std::chrono::system_clock::time_point) override;
135 std::optional<HookOutboxEntry> reconcile(std::string_view, HookExecutionState,
136 std::string_view, std::uint64_t,
137 HookExecutionReceipt) override;
138 std::vector<HookOutboxEntry> pending() const override;
139 std::vector<HookOutboxEntry> reconciliation_required() const override;
140 std::optional<HookOutboxEntry> get(std::string_view) const override;
141private:
142 class Impl;
143 std::unique_ptr<Impl> impl_;
144};
145
148 HookExecutionReceipt receipt;
149 std::vector<ContextArtifact> artifacts;
150};
151
152using HookExecutionBackend = std::function<asio::awaitable<HookExecutionAttempt>(
153 const HookInvocation&,
154 const RuntimeEvent&,
155 std::uint32_t attempt,
157
158using HookArtifactPublisher = std::function<void(
159 const HookInvocation&,
160 const RuntimeEvent&,
161 const std::vector<ContextArtifact>&)>;
162
164class NEOGRAPH_API ContextStoreHookArtifactPublisher final {
165public:
166 explicit ContextStoreHookArtifactPublisher(std::shared_ptr<ContextStore> store);
167 void publish(const HookInvocation& invocation,
168 const RuntimeEvent& event,
169 const std::vector<ContextArtifact>& artifacts) const;
170private:
171 std::shared_ptr<ContextStore> store_;
172};
173
175class NEOGRAPH_API MandatoryHookRunner final {
176public:
177 MandatoryHookRunner(std::shared_ptr<HookJournal> journal, std::shared_ptr<const HookRegistry> registry,
178 std::shared_ptr<const NativeHookExecutionAdapter> adapter,
179 std::string worker_id = "local-hook-runner");
180 MandatoryHookRunner(std::shared_ptr<HookJournal> journal,
181 std::shared_ptr<const HookRegistry> registry,
182 HookExecutionBackend backend,
183 HookArtifactPublisher artifact_publisher = {},
184 std::string worker_id = "hook-backend-runner");
191 asio::awaitable<void> run_async(const RuntimeEvent& event,
192 std::chrono::system_clock::time_point deadline,
193 std::uint32_t max_attempts = 1);
195 [[nodiscard]] bool blocks(const RuntimeEvent& event) const;
196private:
197 friend class HookRuntime;
199 enum class Delivery : std::uint8_t { Fresh, Delivered, InProgress, Unresolved, NotDelivered };
200 [[nodiscard]] Delivery delivery(const RuntimeEvent& event) const;
201 void lost_settlement(const HookInvocation& invocation,
202 std::chrono::system_clock::time_point deadline, HookOutboxEntry& entry,
203 const HookOutboxEntry& leased) const;
204 std::shared_ptr<HookJournal> journal_;
205 std::shared_ptr<const HookRegistry> registry_;
206 std::shared_ptr<const NativeHookExecutionAdapter> adapter_;
207 HookExecutionBackend backend_;
208 HookArtifactPublisher artifact_publisher_;
209 std::string worker_id_;
210};
211
212} // namespace neograph
@ Cancelled
"cancelled" — cancelled via session/cancel.
Owner-scoped idempotent publisher for HookOutput artifacts.
Typed failure of a protected runtime boundary.
Definition hook_outbox.h:85
Immutable durable terminal-attempt record.
Definition hook_outbox.h:41
Atomic durable-outbox abstraction.
Definition hook_outbox.h:99
Immutable snapshot of one logical invocation's durable outbox state.
Definition hook_outbox.h:68
Owns lifecycle-event identity and dispatch for one admitted host runtime.
Plans, journals, and executes mandatory hooks without owning lifecycle call sites.
asio::awaitable< void > run_async(const RuntimeEvent &event, std::chrono::system_clock::time_point deadline, std::uint32_t max_attempts=1)
Runs every Hook the registry plans for event.
bool blocks(const RuntimeEvent &event) const
Whether the registry plans a BlockingMandatory Hook for event.
Host-admitted, deterministic mandatory runtime hooks.
A receipt from the external effect boundary.
Definition hook_outbox.h:25
One transport-neutral Hook execution attempt and its context evidence.
Per-invocation context for a cooperative asynchronous tool.
Definition tool.h:39