Skip to Content

Go SDK

⏱ Running in 5 minutes — new here? Jump to the quickstart for install → first query → first agent memory, or copy the Quick start block below. go get github.com/OpenWorkBench-Co/relata-go and you’re ready.

The official Go SDK for RelataDB. Stdlib-only (no external dependencies), context-based blocking API, fluent query builder, Mem0-style Memory client, 15 typed v1.1 clients, and RFC 7807 problem+json-aware errors.

  • Source: sdks/go/ (sdks/go/relata/)
  • Module: github.com/OpenWorkBench-Co/RelataDB/sdks/go
  • Go: 1.25+
  • Dependencies: none (stdlib only — net/http, encoding/json, crypto/rand, context, time)

Install

go get github.com/OpenWorkBench-Co/RelataDB/sdks/go

Quick start

package main import ( "context" "fmt" "log" "github.com/OpenWorkBench-Co/RelataDB/sdks/go/relata" ) func main() { client := relata.New("http://localhost:9090", &relata.ClientOptions{ BearerToken: "your-token", DefaultPurpose: "analytics", }) result, err := client.Query(context.Background(), "SELECT * FROM Person LIMIT 10") if err != nil { log.Fatal(err) } fmt.Printf("%d rows in %d ms\n", result.RowCount, result.ElapsedMs) result.Each(func(row map[string]any) { fmt.Println(row) }) }

Agent memory

Memory is a Mem0-style surface over the governed /memory/* verbs — purpose + ACL stay on by default. It owns its own transport (it does not take a *Client).

m, err := relata.NewMemory("http://localhost:9090", "agent-notes", &relata.MemoryOptions{BearerToken: token}) if err != nil { log.Fatal(err) // ErrPurposeRequired when purpose is empty } defer // Memory has no Close — net/http needs none id, _ := m.Add(ctx, "Alice prefers dark mode") // store → memory id hits, _ := m.Search(ctx, "ui preferences", relata.WithTopK(5)) // recall dec, _ := m.Forget(ctx, id) // governed retract, not a hard delete

The full cognitive-verb surface (Add, AddBatch, Search, Get, Update, Forget, Associate, Episodes, Justify, Resolve, Summarise) is documented in the Memory table below.

Context & cancellation

Every method takes a context.Context as its first argument. Cancel the context to abort an in-flight request; the SDK surfaces context.DeadlineExceeded / context.Canceled as a *RelataError wrapping ErrConnection. There are no separate async types — Go’s context IS the cancellation mechanism, which is the idiomatic parity for the Python SDK’s sync + async split.

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() result, err := client.Query(ctx, "SELECT * FROM Person")

Client — the main client

client := relata.New(baseURL string, opts *relata.ClientOptions) *relata.Client

New is thread-safe — a single *Client may be shared across goroutines (it shares one *http.Client, like *sql.DB).

Methods

MethodReturnsDescription
Query(ctx, sql, opts...)(*QueryResult, error)Execute a SQL query
Health(ctx)(*HealthResponse, error)GET /health
Status(ctx)(*StatusResponse, error)GET /status — profile, role, quota
Stats(ctx)(*Stats, error)GET /debug/stats — records / states / snapshot_rows / log_leaves / tokens
Version(ctx)(*VersionInfo, error)GET /version — build info
Ready(ctx)(*ReadyReport, error)GET /health/ready — 9-condition readiness
AuditCount(ctx)(*AuditCountResponse, error)GET /audit/count — returns ErrChainCorrupted if ChainValid == false
ClusterNodes(ctx)([]ClusterNode, error)GET /cluster/nodes
IngestDocument(ctx, chunksJSONL, manifestJSON)(*IngestDocumentResponse, error)POST /ingest/document — datagrep-envelope document ingest

ClientOptions

type ClientOptions struct { BearerToken string // Authorization: Bearer <token> DefaultPurpose string // applied when WithPurpose is not passed Timeout time.Duration // per-request timeout (default 30s) HTTPClient *http.Client // inject for mTLS, proxies, tracing // Multi-tenant + delegation (#220, ADR-132) Tenant string // X-Organization-Id (multi-tenant) ActingAs string // X-Acting-As (delegation) DelegatedBy string // X-Delegated-By // Static caller headers (win over SDK defaults; X-Request-ID pinned here // disables the per-request auto-generation) Headers map[string]string // Transport hardening (mirrors _http.py) MaxRetries int // retry 502/503/504 + network errors (default 0) RetryBackoff time.Duration // exponential base (default 500ms) }

Purpose (mandatory)

Every Relata query must declare a purpose registered in the tenant’s PurposeRegistry (SPECS §5.22.4). Calling Query without a purpose (and no DefaultPurpose) returns relata.ErrPurposeRequired before any network call is made.

// Default on the client client := relata.New(url, &relata.ClientOptions{DefaultPurpose: "investigation"}) // Or override per-call result, err := client.Query(ctx, sql, relata.WithPurpose("audit"))

Transport hardening

The Go SDK mirrors the Python _http.py transport layer exactly:

  • X-Request-ID is auto-generated per request (a v4 UUID via crypto/rand, no external deps). Pin your own in Headers["X-Request-ID"] and that value wins on every request — useful for distributed-tracing correlation.
  • Retry on HTTP 502 / 503 / 504 and network errors, with exponential backoff (RetryBackoff * 2^attempt). Off by default; opt in via MaxRetries (capped at 5 attempts).
  • RFC 7807 problem+json parsing — every error carries Code, TypeURL, Retryable, RequestID, and RetryAfter (from the Retry-After header).

Authentication

Set BearerToken in ClientOptions. The token is sent as Authorization: Bearer <token> on every request. Deployments with RELATA_BEARER_TOKEN unset on the server accept unauthenticated requests.

Multi-tenant + delegation headers

client := relata.New(url, &relata.ClientOptions{ BearerToken: token, Tenant: "org-acme", // X-Organization-Id ActingAs: "user-bob", // X-Acting-As DelegatedBy: "user-alice", // X-Delegated-By })

QueryBuilder — fluent SQL

result, err := relata.NewQuery("SELECT * FROM Person"). Purpose("investigation"). Where("nationality = 'PK'"). Where("risk_score > 0.7"). // multiple .Where() → AND AsOf("2025-01-01"). // bi-temporal point-in-time WithProvenance(). // attach PROV-O lineage columns Limit(50). Execute(ctx, client)

Inspect the generated SQL before executing:

sql := relata.NewQuery("SELECT * FROM Person"). Where("risk_score > 0.7"). AsOf("2025-01-01"). SQL() // → SELECT * FROM Person WHERE risk_score > 0.7 AS OF '2025-01-01'
MethodDescription
.Purpose(p)Set the purpose for this query
.Where(predicate)Append AND predicate
.AsOf(timestamp)Bi-temporal AS OF clause
.WithProvenance()Append WITH PROVENANCE
.Limit(n)Set LIMIT
.Timeout(d)Per-request timeout override
.SQL()Return assembled SQL string
.Execute(ctx, client)Execute against a *Client

Convenience constructors

// Graph traversal: Pregel BFS between two entities result, err := relata.PathsBetween("entity-A", "entity-B", 4). Purpose("investigation").Execute(ctx, client) // Biometric face search result, err := relata.MatchFace("s3://faces/suspect.jpg", 10). Purpose("investigation").Execute(ctx, client) // IdentityIndex resolver (phone, Aadhaar, IMEI, IP, …) result, err := relata.LookupIdentity("+919876543210"). Purpose("investigation").Execute(ctx, client) // BM25 + vector hybrid search result, err := relata.HybridSearch("Person", "Ahmed Khalil Karachi", 25). Purpose("analysis").Execute(ctx, client)

Memory — governed agent memory

NewMemory(baseURL, purpose, opts) constructs a standalone Memory client (it owns its own *http.Client, mirroring Python where Memory has its own transport). A non-empty purpose is required — governance is always on.

m, err := relata.NewMemory(url, "agent-notes", &relata.MemoryOptions{ BearerToken: token, SessionID: "sess-1", })
MethodReturnsDescription
Add(ctx, content, opts...)(string, error)Store a memory → id
AddBatch(ctx, items, opts...)([]string, error)Batch store → ids in order
Search(ctx, query, opts...)([]map[string]any, error)Recall (confidence × recency × relevance)
Get(ctx, id)(map[string]any, error)Fetch one memory (nil if absent)
Update(ctx, id, content)(string, error)Supersede → new id
Forget(ctx, id)(map[string]any, error)Governed retention-policy retract
Associate(ctx, src, tgt, rel, opts...)(map[string]any, error)Link two memories
Episodes(ctx, opts...)([]map[string]any, error)List episodes
Justify(ctx, id)(map[string]any, error)PROV-O derivation chain
Resolve(ctx, id, opts...)(map[string]any, error)Resolve a contradiction
Summarise(ctx, ids, opts...)(map[string]any, error)Produce a summary belief

Per-call options use functional setters: WithTopK, WithConfidence, WithMemoryClass, WithSessionID, WithAsOf, WithPolicy, WithSummaryContent. Forget is a governed retention-policy retract, not a hard delete — it returns the policy decision (memory_item_id, policy, forget_at_ns).

v1.1 typed clients — New<Type>FromClient

Each typed client inherits the parent client’s auth, tenant, and purpose context. Construct with the New<Type>FromClient(c *relata.Client) factory, which mirrors the Python from_client classmethod. Optional parameters use struct-pointer options (*Options) matching the ClientOptions style — pass nil for defaults.

ModuleConstructorSurface
relata.governanceNewGovernanceClientRules, Sigma import, retention (holds + WORM), breakglass, alerts, DSAR
relata.mcpNewMcpClient22 MCP tool wrappers + generic CallTool + Initialize/ListTools
relata.a2aNewA2AClientA2A task submit/poll + LangGraph checkpoints + agent card
relata.auditNewAuditClientAudit entries (filtered/paginated) + signed receipts + PDF export
relata.identityNewIdentityClientIdentity label/uncertainty + lookup tables + ERASE SUBJECT
relata.objectsNewObjectClientTyped upsert + batch via /ingest?object_type= (NDJSON)
relata.ingestNewIngestClientBulk NDJSON + CSV + media status
relata.vectorsNewVectorClientKNN + hybrid search + similar-to (SQL-backed)
relata.s3NewS3ClientNative net/http wrapper for the S3 protocol door (HTTP())
relata.systemNewSystemClientLLM config + test + jobs status
relata.streamingNewStreamingClientNDJSON RowIterator + SSE SSEIterator + Arrow BytesIterator
relata.tenantsNewTenantAdminClientTenant CRUD + quota + sharing agreements + platform admin
relata.backupNewBackupClientBackup / restore / PITR (admin-auth)
relata.tokensNewTokenClientDedup / uniqueness tokens (test-and-set, check, revoke, stats)
relata.logNewLogClientOrdered integrity log (append, head, load leaves)

Example — typed client chain

client := relata.New(url, &relata.ClientOptions{ BearerToken: token, DefaultPurpose: "compliance", Tenant: "org-acme", }) mcp := relata.NewMcpClient(client) gov := relata.NewGovernanceClient(client) profile, _ := mcp.GetEntityProfile(ctx, "person-123", "compliance") holds, _ := gov.ListLegalHolds(ctx) sigma, _ := gov.ImportSigma(ctx, sigmaYAML)

Streaming iterators

The streaming surface uses idiomatic Go iterators (like *sql.Rows):

s := relata.NewStreamingClient(client) it, err := s.QueryRows(ctx, "SELECT * FROM Person", nil) // *RowIterator if err != nil { log.Fatal(err) } defer it.Close() for it.Next() { row := it.Row() fmt.Println(row["name"]) } if err := it.Err(); err != nil { log.Fatal(err) }

Watch(ctx, sql, opts) and Alerts(ctx, opts) return *SSEIterator with .Next() / .Event() / .Close() and optional exponential-backoff reconnect. QueryArrowRaw(ctx, sql, opts) returns a *BytesIterator that satisfies io.Reader for Arrow IPC consumption.

Response structs

StructFields
QueryResultRows, QueryID, ElapsedMs, RowCount, Columns — normalises the wire shape (rows-as-int + data array)
HealthResponseStatus, Profile, NodeID + IsHealthy()
StatusResponseProfile, Role, QueryQuota
AuditCountResponseEntries, ChainValid + IsTampered()
ClusterNodeNodeID, Role, URL
VersionInfoVersion, Commit, Profile, SchemaVersion, Features
StatsRecords, States, SnapshotRows, LogLeaves, Tokens, Raw (full server response)
ReadyReportIsReady, Status, Reason, Detail
IngestDocumentResponseReportID, ChunksIngested, Warnings, SchemaVersion, QueueDepth
BackupRefSnapshotID, Kind, WrittenAtNS, SizeBytes, SHA256, RowCount, Path
RestoreStatusRestoreID, SnapshotID, Status, RTOActualSecs, Error + IsCompleted() / IsFailed()
TokenStatsCount, OldestNS, EvictionPolicy
LogLeafIndex, Data, CommitNS

QueryResult is normalised automatically: the server sends {"rows": <int>, "data": [...]} on the wire and the SDK surfaces Rows as a slice regardless.

result, _ := client.Query(ctx, "SELECT * FROM Person LIMIT 10") for _, row := range result.Rows { // iterate fmt.Println(row["name"]) } names := result.Column("name") // []any, nil for missing values fmt.Printf("%d rows in %d ms\n", result.RowCount, result.ElapsedMs)

Error handling

Sentinel errors for the common governance / ops failure modes, plus a typed *RelataError carrying HTTP status, body, and RFC 7807 fields:

import "errors" _, err := client.Query(ctx, sql) switch { case errors.Is(err, relata.ErrPurposeRequired): // No purpose declared — add WithPurpose or set DefaultPurpose case errors.Is(err, relata.ErrUnauthorized): // Bearer token missing or revoked (401) case errors.Is(err, relata.ErrForbidden): // Cedar ACL denied the query (403) case errors.Is(err, relata.ErrNotFound): // Resource not found (404) case errors.Is(err, relata.ErrConflict): // Version / uniqueness conflict (409) case errors.Is(err, relata.ErrValidation): // Request body failed validation (422) case errors.Is(err, relata.ErrRateLimited): // Rate limit or quota exhausted (429) — ErrQuotaExhausted is an alias case errors.Is(err, relata.ErrServerError): // 5xx (after retries exhausted) case errors.Is(err, relata.ErrConnection): // Network error / timeout / server unreachable case errors.Is(err, relata.ErrChainCorrupted): // Audit hash chain integrity failure — treat as a security event } // HTTP status + RFC 7807 fields via type assertion var re *relata.RelataError if errors.As(err, &re) { fmt.Println(re.StatusCode, re.Message) // HTTP 403: Forbidden: policy deny fmt.Println(re.Code) // RELATA.ACL.DENY fmt.Println(re.TypeURL) // https://relata.io/errors/forbidden fmt.Println(re.RequestID) // rid-xyz fmt.Println(re.RetryAfter) // 5s (from Retry-After header) }

ErrQuotaExhausted is kept as an alias for ErrRateLimited for backward compatibility — errors.Is(err, relata.ErrQuotaExhausted) still matches HTTP 429.

Custom HTTP client

Inject a custom *http.Client for mutual TLS, proxies, or distributed tracing:

transport := &http.Transport{TLSClientConfig: tlsCfg} client := relata.New(url, &relata.ClientOptions{ HTTPClient: &http.Client{ Transport: transport, Timeout: 45 * time.Second, }, })

Custom object types

RelataDB is ontology-governed — unknown types are rejected on both ingest and read (fail-closed). Register custom types via the HTTP surface, then upsert through ObjectClient:

client := relata.New(url, &relata.ClientOptions{BearerToken: tok}) oc := relata.NewObjectClient(client) oc.Upsert(ctx, "AgentTask", "t-1", map[string]any{"task_id": "t-1", "status": "done"}, nil)

For ACL access in strict mode, grant via env var: RELATA_ACL_GRANT=AgentTask:read+write.

S3 protocol door

The S3 door is reachable via a native net/http wrapper (no boto3, no external S3 library). NewS3Client(c).HTTP(nil) returns a *http.Client pre-configured with bearer + tenant headers; callers issue standard S3 REST verbs against /<bucket>/<key>:

s3 := relata.NewS3Client(client) hc := s3.HTTP(nil) req, _ := http.NewRequestWithContext(ctx, "PUT", s3.BaseURL()+"/acme-intel/report.pdf", bytes.NewReader(data)) resp, err := hc.Do(req)

Examples

ExampleDescription
examples/basic/Health check + simple SELECT
examples/investigation/Full FIU investigation workflow (identity → transactions → CDR)
examples/face_search/Biometric MATCH_FACE + identity resolution
examples/graph_traversal/PATHS_BETWEEN Pregel BFS + edge distribution
examples/audit/Daily audit-chain verification + compliance report

Run any example against a local server:

./target/debug/relata serve & go run ./sdks/go/examples/basic -url http://localhost:9090 go run ./sdks/go/examples/investigation \ -url http://localhost:9090 \ -phone "+919876543210"

SQL extensions reference

Clause / OperatorDescription
AS OF 'timestamp'Bi-temporal point-in-time read (SPECS §5.5)
WITH PROVENANCEAttach PROV-O lineage columns to every row (SPECS §5.9)
PATHS_BETWEEN(a, b, max_hops => N)Pregel BFS graph traversal (SPECS §5.13)
MATCH_FACE(ref, top_k => N)Biometric face similarity search
LOOKUP_IDENTITY(id)IdentityIndex resolver (SPECS §5.8)
HYBRID_SCORE(query)BM25 + vector combined score (SPECS §5.14)

See the SQL reference for full syntax.

Environment variables

The SDK reads no environment variables directly — all configuration is explicit on ClientOptions / MemoryOptions. The server, however, honours:

VariableEffect
RELATA_BEARER_TOKENWhen unset, the server accepts unauthenticated requests
RELATA_PROFILElite / server / cluster deployment profile
RELATA_DOMAIN_PROFILEenterprise / lea / finint / security / custom
RELATA_ACL_GRANT<Type>:read+write grants for strict-mode ACL

License

AGPL-3.0-only — see the root LICENSE file.

Last updated on