1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# frozen_string_literal: true
# Low-level FFI bindings that map directly to the agentdb C shared library.
#
# Consumers should use the higher-level AgentDB::Database and
# AgentDB::Collection classes rather than calling these functions directly.
#
# Memory contract:
# - All char* values returned by agentdb_* functions are heap-allocated.
# They MUST be freed with agentdb_free_string() — never Ruby's GC alone.
# - Input const char* parameters are borrowed for the duration of the call.
# - AgentDbHandle* is opaque; always close with agentdb_close().
extend FFI::Library
# ── Library loading ────────────────────────────────────────────────────
# Build the candidate list in priority order so we find the library on
# every supported platform without hard-coding an absolute path.
base =
search_dirs = [
# Sibling cargo output (when running from the repo)
File.expand_path(, __dir__),
File.expand_path(, __dir__),
# Standard system locations
,
,
# macOS Homebrew prefix
,
]
candidates = [, ]
# Add directory-qualified paths for each extension
exts = case RbConfig::CONFIG[]
when then [, ]
when then []
else []
end
paths = []
search_dirs.each do
candidates.each do
exts.each do
paths << File.join(dir, )
end
end
end
# Also try bare names so FFI can apply its own search path logic
candidates.each do
exts.each { paths << }
end
paths << base # last-resort: let FFI search LD_LIBRARY_PATH / PATH
paths
end
begin
ffi_lib lib_candidates
rescue LoadError => e
raise AgentDB::LibraryNotFoundError,
\
\
\
end
# ── Type aliases ────────────────────────────────────────────────────────
#
# :size_t maps to C uintptr_t / size_t (used for dim, top_k, etc.)
# :int64 maps to C int64_t (used for execute row count)
# :int32 maps to C int32_t (used for boolean-style return codes)
# :double maps to C double
# :float maps to C float (only appears as pointer-to-float array)
# ── Lifecycle ──────────────────────────────────────────────────────────
# char *agentdb_last_error(void);
attach_function :agentdb_last_error, [], :pointer
# void agentdb_free_string(char *ptr);
attach_function :agentdb_free_string, [:pointer], :void
# AgentDbHandle *agentdb_open(const char *path);
attach_function :agentdb_open, [:string], :pointer
# void agentdb_close(AgentDbHandle *handle);
attach_function :agentdb_close, [:pointer], :void
# ── SQL ────────────────────────────────────────────────────────────────
# int64_t agentdb_execute(AgentDbHandle *handle, const char *sql);
attach_function :agentdb_execute, [:pointer, :string], :int64
# char *agentdb_query_json(AgentDbHandle *handle, const char *sql);
attach_function :agentdb_query_json, [:pointer, :string], :pointer
# char *agentdb_query_json_params(AgentDbHandle *handle, const char *sql,
# const char *params_json);
attach_function :agentdb_query_json_params, [:pointer, :string, :string], :pointer
# ── Vector store ───────────────────────────────────────────────────────
# int32_t agentdb_vector_upsert(AgentDbHandle *handle,
# const char *collection, const char *id,
# const float *vector, uintptr_t dim,
# const char *metadata);
attach_function :agentdb_vector_upsert,
[:pointer, :string, :string, :pointer, :size_t, :string],
:int32
# char *agentdb_vector_search(AgentDbHandle *handle,
# const char *collection,
# const float *query, uintptr_t dim,
# uintptr_t top_k,
# const char *filter_json);
attach_function :agentdb_vector_search,
[:pointer, :string, :pointer, :size_t, :size_t, :string],
:pointer
# int32_t agentdb_vector_delete(AgentDbHandle *handle,
# const char *collection, const char *id,
# uintptr_t dim);
attach_function :agentdb_vector_delete, [:pointer, :string, :string, :size_t], :int32
# int32_t agentdb_drop_collection(AgentDbHandle *handle,
# const char *collection);
attach_function :agentdb_drop_collection, [:pointer, :string], :int32
# int32_t agentdb_reindex(AgentDbHandle *handle,
# const char *collection, uintptr_t dim);
attach_function :agentdb_reindex, [:pointer, :string, :size_t], :int32
# ── Memory graph ───────────────────────────────────────────────────────
# int32_t agentdb_graph_add_node(AgentDbHandle *handle,
# const char *id, const char *kind,
# const char *data_json);
attach_function :agentdb_graph_add_node, [:pointer, :string, :string, :string], :int32
# int32_t agentdb_graph_add_edge(AgentDbHandle *handle,
# const char *src, const char *dst,
# const char *relation, double weight);
attach_function :agentdb_graph_add_edge, [:pointer, :string, :string, :string, :double], :int32
# char *agentdb_graph_neighbors(AgentDbHandle *handle,
# const char *node_id, uintptr_t max_depth,
# double min_weight, const char *relation);
attach_function :agentdb_graph_neighbors,
[:pointer, :string, :size_t, :double, :string],
:pointer
# char *agentdb_graph_get_node(AgentDbHandle *handle, const char *id);
attach_function :agentdb_graph_get_node, [:pointer, :string], :pointer
# int32_t agentdb_graph_delete_node(AgentDbHandle *handle, const char *id);
attach_function :agentdb_graph_delete_node, [:pointer, :string], :int32
# int32_t agentdb_graph_delete_edge(AgentDbHandle *handle,
# const char *src, const char *dst,
# const char *relation);
attach_function :agentdb_graph_delete_edge, [:pointer, :string, :string, :string], :int32
# ── Full-text search ───────────────────────────────────────────────────
# int32_t agentdb_fts_index(AgentDbHandle *handle,
# const char *collection, const char *vec_id,
# const char *collection_id, const char *text);
attach_function :agentdb_fts_index, [:pointer, :string, :string, :string, :string], :int32
# char *agentdb_fts_search(AgentDbHandle *handle,
# const char *collection, const char *query,
# uintptr_t top_k);
attach_function :agentdb_fts_search, [:pointer, :string, :string, :size_t], :pointer
# int32_t agentdb_fts_delete(AgentDbHandle *handle,
# const char *collection, const char *vec_id);
attach_function :agentdb_fts_delete, [:pointer, :string, :string], :int32
# int32_t agentdb_fts_optimize(AgentDbHandle *handle,
# const char *collection);
attach_function :agentdb_fts_optimize, [:pointer, :string], :int32
# ── Hybrid query ───────────────────────────────────────────────────────
# char *agentdb_hybrid_query(AgentDbHandle *handle,
# const char *anchor_node,
# const float *embedding, uintptr_t dim,
# const char *collection,
# uintptr_t graph_depth, uintptr_t top_k,
# double alpha, const char *filter_json);
attach_function :agentdb_hybrid_query,
[:pointer, :string, :pointer, :size_t, :string,
:size_t, :size_t, :double, :string],
:pointer
# ── Stats ──────────────────────────────────────────────────────────────
# char *agentdb_stats(AgentDbHandle *handle);
attach_function :agentdb_stats, [:pointer], :pointer
# ── Conversations ──────────────────────────────────────────────────────
# int32_t agentdb_conversation_create(AgentDbHandle *handle,
# const char *id, const char *title,
# const char *metadata);
attach_function :agentdb_conversation_create, [:pointer, :string, :string, :string], :int32
# char *agentdb_conversation_add_message(AgentDbHandle *handle,
# const char *conversation_id,
# const char *role, const char *content,
# const char *metadata);
attach_function :agentdb_conversation_add_message,
[:pointer, :string, :string, :string, :string],
:pointer
# char *agentdb_conversation_get_messages(AgentDbHandle *handle,
# const char *conversation_id,
# uintptr_t limit);
attach_function :agentdb_conversation_get_messages, [:pointer, :string, :size_t], :pointer
# char *agentdb_conversation_list(AgentDbHandle *handle);
attach_function :agentdb_conversation_list, [:pointer], :pointer
# int32_t agentdb_conversation_delete(AgentDbHandle *handle, const char *id);
attach_function :agentdb_conversation_delete, [:pointer, :string], :int32
# ── Workflows ──────────────────────────────────────────────────────────
# int32_t agentdb_workflow_create(AgentDbHandle *handle,
# const char *id, const char *name,
# const char *input, const char *metadata);
attach_function :agentdb_workflow_create,
[:pointer, :string, :string, :string, :string],
:int32
# char *agentdb_workflow_add_step(AgentDbHandle *handle,
# const char *workflow_id, const char *name,
# const char *input);
attach_function :agentdb_workflow_add_step, [:pointer, :string, :string, :string], :pointer
# int32_t agentdb_workflow_update_step(AgentDbHandle *handle,
# const char *step_id, const char *status,
# const char *output, const char *error);
attach_function :agentdb_workflow_update_step,
[:pointer, :string, :string, :string, :string],
:int32
# int32_t agentdb_workflow_complete(AgentDbHandle *handle,
# const char *id, const char *output);
attach_function :agentdb_workflow_complete, [:pointer, :string, :string], :int32
# int32_t agentdb_workflow_fail(AgentDbHandle *handle,
# const char *id, const char *error);
attach_function :agentdb_workflow_fail, [:pointer, :string, :string], :int32
# char *agentdb_workflow_get(AgentDbHandle *handle, const char *id);
attach_function :agentdb_workflow_get, [:pointer, :string], :pointer
# char *agentdb_workflow_list(AgentDbHandle *handle,
# const char *status_filter);
attach_function :agentdb_workflow_list, [:pointer, :string], :pointer
# ── Traces ─────────────────────────────────────────────────────────────
# char *agentdb_trace_add(AgentDbHandle *handle,
# const char *session_id, const char *parent_id,
# const char *trace_type, const char *content,
# const char *metadata);
attach_function :agentdb_trace_add,
[:pointer, :string, :string, :string, :string, :string],
:pointer
# char *agentdb_trace_get_by_session(AgentDbHandle *handle,
# const char *session_id);
attach_function :agentdb_trace_get_by_session, [:pointer, :string], :pointer
# char *agentdb_trace_get_tree(AgentDbHandle *handle, const char *root_id);
attach_function :agentdb_trace_get_tree, [:pointer, :string], :pointer
# ── Helpers ────────────────────────────────────────────────────────────
# Read a heap-allocated C string returned by the library, then immediately
# free it. Returns the Ruby String or nil if ptr is NULL.
return nil if ptr.null?
begin
ptr.read_string.dup.force_encoding(Encoding::UTF_8)
ensure
agentdb_free_string(ptr)
end
end
# Read the current last-error string and clear it by reading.
# Returns nil if there is no pending error.
ptr = agentdb_last_error
read_and_free(ptr)
end
# Pack a Ruby Array of Floats into a native float32 buffer suitable for
# passing to vector functions. Returns [FFI::MemoryPointer, size].
floats = array.map(&:to_f)
buf = FFI::MemoryPointer.new(:float, floats.size)
buf.put_array_of_float(0, floats)
[buf, floats.size]
end
end
end