package agentdb
import "C"
import (
"encoding/json"
"errors"
"fmt"
"runtime"
"unsafe"
)
func lastError(fallback string) error {
ptr := C.agentdb_last_error()
if ptr == nil {
return errors.New(fallback)
}
msg := C.GoString(ptr)
C.agentdb_free_string(ptr)
return errors.New(msg)
}
type DB struct {
handle *C.AgentDbHandle
}
type Stats struct {
Collections int64 `json:"collections"`
Vectors int64 `json:"vectors"`
Nodes int64 `json:"nodes"`
Edges int64 `json:"edges"`
Conversations int64 `json:"conversations"`
Messages int64 `json:"messages"`
Workflows int64 `json:"workflows"`
WorkflowSteps int64 `json:"workflow_steps"`
Traces int64 `json:"traces"`
}
type VectorResult struct {
ID string `json:"id"`
Score float64 `json:"score"`
Metadata json.RawMessage `json:"metadata"`
}
type GraphNode struct {
ID string `json:"id"`
Kind string `json:"kind"`
Depth int `json:"depth"`
Weight float64 `json:"weight"`
Data json.RawMessage `json:"data"`
}
type FTSResult struct {
ID string `json:"id"`
Snippet string `json:"snippet"`
Rank float64 `json:"rank"`
}
type HybridResult struct {
ID string `json:"id"`
RankScore float64 `json:"rank_score"`
VectorScore float64 `json:"vector_score"`
GraphWeight float64 `json:"graph_weight"`
}
func Open(path string) (*DB, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
handle := C.agentdb_open(cpath)
if handle == nil {
return nil, lastError("agentdb_open returned nil")
}
db := &DB{handle: handle}
runtime.SetFinalizer(db, (*DB).Close)
return db, nil
}
func (db *DB) Close() {
if db.handle != nil {
C.agentdb_close(db.handle)
db.handle = nil
runtime.SetFinalizer(db, nil)
}
}
func (db *DB) Execute(sql string) (int64, error) {
csql := C.CString(sql)
defer C.free(unsafe.Pointer(csql))
n := C.agentdb_execute(db.handle, csql)
if n == -1 {
return -1, lastError("agentdb_execute failed")
}
return int64(n), nil
}
func (db *DB) QueryJSON(sql string) (string, error) {
csql := C.CString(sql)
defer C.free(unsafe.Pointer(csql))
ptr := C.agentdb_query_json(db.handle, csql)
if ptr == nil {
return "", lastError("agentdb_query_json failed")
}
result := C.GoString(ptr)
C.agentdb_free_string(ptr)
return result, nil
}
func (db *DB) VectorUpsert(collection, id string, vector []float32, metadata []byte) error {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var cmeta *C.char
if metadata != nil {
cmeta = C.CString(string(metadata))
defer C.free(unsafe.Pointer(cmeta))
}
if len(vector) == 0 {
return errors.New("vector must not be empty")
}
cvec := (*C.float)(unsafe.Pointer(&vector[0]))
rc := C.agentdb_vector_upsert(db.handle, ccol, cid, cvec, C.ulong(len(vector)), cmeta)
if rc != 0 {
return lastError("agentdb_vector_upsert failed")
}
return nil
}
func (db *DB) VectorSearch(collection string, query []float32, topK int, filterJSON []byte) ([]VectorResult, error) {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
var cfilter *C.char
if filterJSON != nil {
cfilter = C.CString(string(filterJSON))
defer C.free(unsafe.Pointer(cfilter))
}
if len(query) == 0 {
return nil, errors.New("query vector must not be empty")
}
cq := (*C.float)(unsafe.Pointer(&query[0]))
ptr := C.agentdb_vector_search(db.handle, ccol, cq, C.ulong(len(query)), C.ulong(topK), cfilter)
if ptr == nil {
return nil, lastError("agentdb_vector_search failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
var results []VectorResult
if err := json.Unmarshal([]byte(raw), &results); err != nil {
return nil, fmt.Errorf("agentdb: parse vector search results: %w", err)
}
return results, nil
}
func (db *DB) GraphAddNode(id, kind string, dataJSON []byte) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
ckind := C.CString(kind)
defer C.free(unsafe.Pointer(ckind))
var cdata *C.char
if dataJSON != nil {
cdata = C.CString(string(dataJSON))
defer C.free(unsafe.Pointer(cdata))
}
rc := C.agentdb_graph_add_node(db.handle, cid, ckind, cdata)
if rc != 0 {
return lastError("agentdb_graph_add_node failed")
}
return nil
}
func (db *DB) GraphAddEdge(src, dst, relation string, weight float64) error {
csrc := C.CString(src)
defer C.free(unsafe.Pointer(csrc))
cdst := C.CString(dst)
defer C.free(unsafe.Pointer(cdst))
crel := C.CString(relation)
defer C.free(unsafe.Pointer(crel))
rc := C.agentdb_graph_add_edge(db.handle, csrc, cdst, crel, C.double(weight))
if rc != 0 {
return lastError("agentdb_graph_add_edge failed")
}
return nil
}
func (db *DB) GraphNeighbors(nodeID string, maxDepth int, minWeight float64, relation string) ([]GraphNode, error) {
cid := C.CString(nodeID)
defer C.free(unsafe.Pointer(cid))
var crel *C.char
if relation != "" {
crel = C.CString(relation)
defer C.free(unsafe.Pointer(crel))
}
ptr := C.agentdb_graph_neighbors(db.handle, cid, C.ulong(maxDepth), C.double(minWeight), crel)
if ptr == nil {
return nil, lastError("agentdb_graph_neighbors failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
var results []GraphNode
if err := json.Unmarshal([]byte(raw), &results); err != nil {
return nil, fmt.Errorf("agentdb: parse graph neighbors: %w", err)
}
return results, nil
}
func (db *DB) FTSIndex(collection, vecID, collectionID, text string) error {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
cvid := C.CString(vecID)
defer C.free(unsafe.Pointer(cvid))
ccid := C.CString(collectionID)
defer C.free(unsafe.Pointer(ccid))
ctxt := C.CString(text)
defer C.free(unsafe.Pointer(ctxt))
rc := C.agentdb_fts_index(db.handle, ccol, cvid, ccid, ctxt)
if rc != 0 {
return lastError("agentdb_fts_index failed")
}
return nil
}
func (db *DB) FTSSearch(collection, query string, topK int) ([]FTSResult, error) {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
cq := C.CString(query)
defer C.free(unsafe.Pointer(cq))
ptr := C.agentdb_fts_search(db.handle, ccol, cq, C.ulong(topK))
if ptr == nil {
return nil, lastError("agentdb_fts_search failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
var results []FTSResult
if err := json.Unmarshal([]byte(raw), &results); err != nil {
return nil, fmt.Errorf("agentdb: parse fts results: %w", err)
}
return results, nil
}
func (db *DB) HybridQuery(anchorNode string, embedding []float32, collection string, graphDepth, topK int, alpha float64, filterJSON []byte) ([]HybridResult, error) {
canchor := C.CString(anchorNode)
defer C.free(unsafe.Pointer(canchor))
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
var cfilter *C.char
if filterJSON != nil {
cfilter = C.CString(string(filterJSON))
defer C.free(unsafe.Pointer(cfilter))
}
if len(embedding) == 0 {
return nil, errors.New("embedding must not be empty")
}
cemb := (*C.float)(unsafe.Pointer(&embedding[0]))
ptr := C.agentdb_hybrid_query(
db.handle, canchor,
cemb, C.ulong(len(embedding)),
ccol,
C.ulong(graphDepth), C.ulong(topK),
C.double(alpha),
cfilter,
)
if ptr == nil {
return nil, lastError("agentdb_hybrid_query failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
var results []HybridResult
if err := json.Unmarshal([]byte(raw), &results); err != nil {
return nil, fmt.Errorf("agentdb: parse hybrid results: %w", err)
}
return results, nil
}
func (db *DB) Stats() (Stats, error) {
ptr := C.agentdb_stats(db.handle)
if ptr == nil {
return Stats{}, lastError("agentdb_stats failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
var s Stats
if err := json.Unmarshal([]byte(raw), &s); err != nil {
return Stats{}, fmt.Errorf("agentdb: parse stats: %w", err)
}
return s, nil
}
func (db *DB) ConversationCreate(id, title string, metadataJSON []byte) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var ctitle *C.char
if title != "" {
ctitle = C.CString(title)
defer C.free(unsafe.Pointer(ctitle))
}
var cmeta *C.char
if metadataJSON != nil {
cmeta = C.CString(string(metadataJSON))
defer C.free(unsafe.Pointer(cmeta))
}
rc := C.agentdb_conversation_create(db.handle, cid, ctitle, cmeta)
if rc != 0 {
return lastError("agentdb_conversation_create failed")
}
return nil
}
func (db *DB) ConversationAddMessage(conversationID, role, content string, metadataJSON []byte) (string, error) {
ccid := C.CString(conversationID)
defer C.free(unsafe.Pointer(ccid))
crole := C.CString(role)
defer C.free(unsafe.Pointer(crole))
ccontent := C.CString(content)
defer C.free(unsafe.Pointer(ccontent))
var cmeta *C.char
if metadataJSON != nil {
cmeta = C.CString(string(metadataJSON))
defer C.free(unsafe.Pointer(cmeta))
}
ptr := C.agentdb_conversation_add_message(db.handle, ccid, crole, ccontent, cmeta)
if ptr == nil {
return "", lastError("agentdb_conversation_add_message failed")
}
result := C.GoString(ptr)
C.agentdb_free_string(ptr)
return result, nil
}
func (db *DB) ConversationGetMessages(conversationID string, limit int) (string, error) {
ccid := C.CString(conversationID)
defer C.free(unsafe.Pointer(ccid))
ptr := C.agentdb_conversation_get_messages(db.handle, ccid, C.ulong(limit))
if ptr == nil {
return "", lastError("agentdb_conversation_get_messages failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
return raw, nil
}
func (db *DB) ConversationList() (string, error) {
ptr := C.agentdb_conversation_list(db.handle)
if ptr == nil {
return "", lastError("agentdb_conversation_list failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
return raw, nil
}
func (db *DB) ConversationDelete(id string) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
rc := C.agentdb_conversation_delete(db.handle, cid)
if rc != 0 {
return lastError("agentdb_conversation_delete failed")
}
return nil
}
func (db *DB) WorkflowCreate(id, name string, inputJSON, metadataJSON []byte) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
var cinput *C.char
if inputJSON != nil {
cinput = C.CString(string(inputJSON))
defer C.free(unsafe.Pointer(cinput))
}
var cmeta *C.char
if metadataJSON != nil {
cmeta = C.CString(string(metadataJSON))
defer C.free(unsafe.Pointer(cmeta))
}
rc := C.agentdb_workflow_create(db.handle, cid, cname, cinput, cmeta)
if rc != 0 {
return lastError("agentdb_workflow_create failed")
}
return nil
}
func (db *DB) WorkflowAddStep(workflowID, name string, inputJSON []byte) (string, error) {
cwid := C.CString(workflowID)
defer C.free(unsafe.Pointer(cwid))
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
var cinput *C.char
if inputJSON != nil {
cinput = C.CString(string(inputJSON))
defer C.free(unsafe.Pointer(cinput))
}
ptr := C.agentdb_workflow_add_step(db.handle, cwid, cname, cinput)
if ptr == nil {
return "", lastError("agentdb_workflow_add_step failed")
}
result := C.GoString(ptr)
C.agentdb_free_string(ptr)
return result, nil
}
func (db *DB) WorkflowUpdateStep(stepID, status string, outputJSON []byte, errMsg string) error {
csid := C.CString(stepID)
defer C.free(unsafe.Pointer(csid))
cstatus := C.CString(status)
defer C.free(unsafe.Pointer(cstatus))
var coutput *C.char
if outputJSON != nil {
coutput = C.CString(string(outputJSON))
defer C.free(unsafe.Pointer(coutput))
}
var cerr *C.char
if errMsg != "" {
cerr = C.CString(errMsg)
defer C.free(unsafe.Pointer(cerr))
}
rc := C.agentdb_workflow_update_step(db.handle, csid, cstatus, coutput, cerr)
if rc != 0 {
return lastError("agentdb_workflow_update_step failed")
}
return nil
}
func (db *DB) WorkflowComplete(id string, outputJSON []byte) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var coutput *C.char
if outputJSON != nil {
coutput = C.CString(string(outputJSON))
defer C.free(unsafe.Pointer(coutput))
}
rc := C.agentdb_workflow_complete(db.handle, cid, coutput)
if rc != 0 {
return lastError("agentdb_workflow_complete failed")
}
return nil
}
func (db *DB) WorkflowGet(id string) (string, error) {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
ptr := C.agentdb_workflow_get(db.handle, cid)
if ptr == nil {
return "", lastError("agentdb_workflow_get failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
return raw, nil
}
func (db *DB) WorkflowList(statusFilter string) (string, error) {
var cfilter *C.char
if statusFilter != "" {
cfilter = C.CString(statusFilter)
defer C.free(unsafe.Pointer(cfilter))
}
ptr := C.agentdb_workflow_list(db.handle, cfilter)
if ptr == nil {
return "", lastError("agentdb_workflow_list failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
return raw, nil
}
func (db *DB) TraceAdd(sessionID, parentID, traceType, content string, metadataJSON []byte) (string, error) {
var csid *C.char
if sessionID != "" {
csid = C.CString(sessionID)
defer C.free(unsafe.Pointer(csid))
}
var cpid *C.char
if parentID != "" {
cpid = C.CString(parentID)
defer C.free(unsafe.Pointer(cpid))
}
ctt := C.CString(traceType)
defer C.free(unsafe.Pointer(ctt))
ccontent := C.CString(content)
defer C.free(unsafe.Pointer(ccontent))
var cmeta *C.char
if metadataJSON != nil {
cmeta = C.CString(string(metadataJSON))
defer C.free(unsafe.Pointer(cmeta))
}
ptr := C.agentdb_trace_add(db.handle, csid, cpid, ctt, ccontent, cmeta)
if ptr == nil {
return "", lastError("agentdb_trace_add failed")
}
result := C.GoString(ptr)
C.agentdb_free_string(ptr)
return result, nil
}
func (db *DB) TraceGetBySession(sessionID string) (string, error) {
csid := C.CString(sessionID)
defer C.free(unsafe.Pointer(csid))
ptr := C.agentdb_trace_get_by_session(db.handle, csid)
if ptr == nil {
return "", lastError("agentdb_trace_get_by_session failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
return raw, nil
}
func (db *DB) TraceGetTree(rootID string) (string, error) {
crid := C.CString(rootID)
defer C.free(unsafe.Pointer(crid))
ptr := C.agentdb_trace_get_tree(db.handle, crid)
if ptr == nil {
return "", lastError("agentdb_trace_get_tree failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
return raw, nil
}
func (db *DB) VectorDelete(collection, id string) error {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
rc := C.agentdb_vector_delete(db.handle, ccol, cid)
if rc != 0 {
return lastError("agentdb_vector_delete failed")
}
return nil
}
func (db *DB) DropCollection(collection string) error {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
rc := C.agentdb_drop_collection(db.handle, ccol)
if rc != 0 {
return lastError("agentdb_drop_collection failed")
}
return nil
}
func (db *DB) Reindex(collection string) error {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
rc := C.agentdb_reindex(db.handle, ccol)
if rc != 0 {
return lastError("agentdb_reindex failed")
}
return nil
}
func (db *DB) GraphGetNode(id string) (string, error) {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
ptr := C.agentdb_graph_get_node(db.handle, cid)
if ptr == nil {
return "", lastError("agentdb_graph_get_node failed")
}
raw := C.GoString(ptr)
C.agentdb_free_string(ptr)
return raw, nil
}
func (db *DB) GraphDeleteNode(id string) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
rc := C.agentdb_graph_delete_node(db.handle, cid)
if rc != 0 {
return lastError("agentdb_graph_delete_node failed")
}
return nil
}
func (db *DB) GraphDeleteEdge(src, dst, relation string) error {
csrc := C.CString(src)
defer C.free(unsafe.Pointer(csrc))
cdst := C.CString(dst)
defer C.free(unsafe.Pointer(cdst))
crel := C.CString(relation)
defer C.free(unsafe.Pointer(crel))
rc := C.agentdb_graph_delete_edge(db.handle, csrc, cdst, crel)
if rc != 0 {
return lastError("agentdb_graph_delete_edge failed")
}
return nil
}
func (db *DB) FTSDelete(collection, vecID string) error {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
cvid := C.CString(vecID)
defer C.free(unsafe.Pointer(cvid))
rc := C.agentdb_fts_delete(db.handle, ccol, cvid)
if rc != 0 {
return lastError("agentdb_fts_delete failed")
}
return nil
}
func (db *DB) FTSOptimize(collection string) error {
ccol := C.CString(collection)
defer C.free(unsafe.Pointer(ccol))
rc := C.agentdb_fts_optimize(db.handle, ccol)
if rc != 0 {
return lastError("agentdb_fts_optimize failed")
}
return nil
}
func (db *DB) WorkflowFail(id, errMsg string) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var cerr *C.char
if errMsg != "" {
cerr = C.CString(errMsg)
defer C.free(unsafe.Pointer(cerr))
}
rc := C.agentdb_workflow_fail(db.handle, cid, cerr)
if rc != 0 {
return lastError("agentdb_workflow_fail failed")
}
return nil
}