Go SDK
Goroutine-safe Go SDK for building AI agents with persistent memory, local SQLite storage, cloud sync, and idiomatic context-based APIs.
go get github.com/rekall-ai/rekall-goGo 1.21+
Installation
go get github.com/rekall-ai/rekall-go
Requirements
Go 1.21 or higher is required. The SDK uses modernc.org/sqlite for pure-Go SQLite support -- no CGo required.
Client Initialization
Create an agent using functional options. All API methods accept a context.Context for cancellation and timeouts.
package mainimport ("context""log""os""os/signal""syscall""time"rekall "github.com/rekall-ai/rekall-go")func main() {agent, err := rekall.NewAgent(rekall.WithAPIKey(os.Getenv("REKALL_API_KEY")),rekall.WithAgentID("my-assistant"),rekall.WithStoragePath("./data/rekall.db"),rekall.WithSyncEnabled(true),rekall.WithSyncInterval(30 * time.Second),rekall.WithSyncStrategy(rekall.LastWriteWins),rekall.WithBaseURL("https://api.rekall.ai/v1"), // Default)if err != nil {log.Fatal(err)}ctx := context.Background()if err := agent.Initialize(ctx); err != nil {log.Fatal(err)}// Graceful shutdownsigCh := make(chan os.Signal, 1)signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)<-sigChshutdownCtx, cancel := context.WithTimeout(ctx, 10*time.Second)defer cancel()if err := agent.Shutdown(shutdownCtx); err != nil {log.Printf("shutdown error: %v", err)}}
Environment variables
If WithAPIKey is not called, the SDK reads from the REKALL_API_KEY environment variable automatically.
Creating Memories
Store memories locally and sync to the cloud. Use struct literals or the builder pattern for constructing inputs.
memory, err := agent.Memories.Create(ctx, &rekall.MemoryCreateInput{Type: rekall.Episodic,Content: "User asked about quarterly revenue trends for Q3 2024.",Context: rekall.AgentContext,Importance: 0.8,Metadata: map[string]interface{}{"conversationId": "conv_abc123","userId": "user_456","tags": []string{"finance", "revenue", "Q3"},},})if err != nil {log.Fatal(err)}fmt.Println(memory.ID) // "mem_..."fmt.Println(memory.CreatedAt) // time.Timefmt.Println(memory.SyncStatus) // "pending" | "synced" | "conflict"
memory, err := agent.Memories.Create(ctx, &rekall.MemoryCreateInput{Type: rekall.Semantic,Content: "Acme Corp is a Fortune 500 company headquartered in San Francisco.",Entities: []rekall.EntityInput{{Name: "Acme Corp", Type: "organization"},{Name: "San Francisco", Type: "location"},},Relationships: []rekall.RelationshipInput{{Source: "Acme Corp",Target: "San Francisco",Type: "headquartered_in",},},})
memory, err := agent.Memories.Create(ctx, &rekall.MemoryCreateInput{Type: rekall.Procedural,Content: "Deploy to production workflow",Steps: []rekall.Step{{Order: 1, Instruction: "Run the test suite", Command: "go test ./..."},{Order: 2, Instruction: "Build the binary", Command: "go build -o app ."},{Order: 3, Instruction: "Deploy to staging", Command: "deploy --env staging"},{Order: 4, Instruction: "Run smoke tests", Command: "go test -tags smoke ./..."},{Order: 5, Instruction: "Promote to production", Command: "deploy --env prod"},},})
Searching Memories
Semantic search with optional filters. Returns scored results from local storage and/or the cloud.
results, err := agent.Memories.Search(ctx, &rekall.MemorySearchInput{Query: "What do we know about the customer onboarding process?",Type: rekall.Ptr(rekall.Procedural), // Optional filterContext: rekall.Ptr(rekall.AgentContext),Limit: 10,Threshold: 0.7,})if err != nil {log.Fatal(err)}for _, result := range results {fmt.Printf("Content: %s", result.Memory.Content)fmt.Printf("Score: %.2f", result.Score)fmt.Printf("Source: %s", result.Source) // "local" or "cloud"}
results, err := agent.Memories.Search(ctx, &rekall.MemorySearchInput{Query: "revenue",Filters: &rekall.SearchFilters{Tags: &rekall.ContainsFilter{Contains: "finance"},CreatedAfter: rekall.Ptr(time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC)),Importance: &rekall.RangeFilter{GTE: rekall.Ptr(0.5)},},Sort: rekall.SortByRelevance,})
Entity Management
Create entities, define relationships, and query the knowledge graph.
// Create an entityentity, err := agent.Entities.Create(ctx, &rekall.EntityCreateInput{Name: "Acme Corp",Type: "organization",Attributes: map[string]interface{}{"industry": "Technology","founded": 2010,"website": "https://acme.example.com",},})if err != nil {log.Fatal(err)}// Create a relationship_, err = agent.Entities.Relate(ctx, &rekall.RelateInput{SourceID: entity.ID,TargetID: "ent_person_456",Type: "employs",Attributes: map[string]interface{}{"role": "CEO", "since": "2018-01-01"},})// Query the knowledge graphgraph, err := agent.Entities.Graph(ctx, &rekall.GraphQueryInput{RootID: entity.ID,Depth: 2,Types: []string{"employs", "headquartered_in", "acquired"},})for _, node := range graph.Nodes {fmt.Printf("%s (%s)", node.Name, node.Type)}for _, edge := range graph.Edges {fmt.Printf("%s --%s--> %s", edge.Source, edge.Type, edge.Target)}// List entitiesentities, err := agent.Entities.List(ctx, &rekall.EntityListInput{Type: rekall.Ptr("organization"),Limit: 50,})
Agent Lifecycle
Spawn child agents, send messages, and manage termination.
// Spawn a child agentchild, err := agent.Spawn(ctx, &rekall.SpawnOptions{AgentID: "research-assistant",Config: rekall.AgentConfig{Model: "claude-3-opus",SystemPrompt: "You are a research assistant...",},Memory: rekall.MemoryOptions{Context: rekall.AgentContext,InheritParent: true,},})if err != nil {log.Fatal(err)}// Send a messageresponse, err := agent.Send(ctx, child.AgentID, &rekall.Message{Type: "task",Payload: map[string]interface{}{"query": "Research latest AI safety papers",},})// Terminate a childerr = agent.Terminate(ctx, child.AgentID, &rekall.TerminateOptions{PreserveMemories: true,})// Get agent statusstatus, err := agent.Status(ctx)fmt.Printf("State: %s, Uptime: %v", status.State, status.Uptime)// List child agentschildren, err := agent.ListChildren(ctx)
Goroutine Safety
The SDK is safe for concurrent use from multiple goroutines. All methods use internal synchronization for both local storage access and API calls.
// Safe to use from multiple goroutinesvar wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func(idx int) {defer wg.Done()memory, err := agent.Memories.Create(ctx, &rekall.MemoryCreateInput{Type: rekall.Episodic,Content: fmt.Sprintf("Memory from goroutine %d", idx),})if err != nil {log.Printf("goroutine %d error: %v", idx, err)return}log.Printf("goroutine %d created memory %s", idx, memory.ID)}(i)}wg.Wait()// Concurrent searches are also safetype searchResult struct {results []rekall.SearchResulterr error}ch := make(chan searchResult, 2)go func() {r, err := agent.Memories.Search(ctx, &rekall.MemorySearchInput{Query: "revenue trends",})ch <- searchResult{r, err}}()go func() {r, err := agent.Memories.Search(ctx, &rekall.MemorySearchInput{Query: "customer feedback",})ch <- searchResult{r, err}}()for i := 0; i < 2; i++ {res := <-chif res.err != nil {log.Printf("search error: %v", res.err)}}
Connection pooling
The SDK maintains an internal HTTP connection pool and a SQLite connection pool. You do not need to manage connections manually. The defaults work well for most workloads, but you can tune pool sizes with WithMaxHTTPConns and WithMaxDBConns.
Error Handling
The SDK returns typed errors that can be inspected with errors.As. All API errors include the HTTP status code, error code, and message.
import "errors"memory, err := agent.Memories.Get(ctx, "mem_nonexistent")if err != nil {var notFound *rekall.NotFoundErrorvar rateLimit *rekall.RateLimitErrorvar authErr *rekall.AuthErrorvar apiErr *rekall.APIErrorswitch {case errors.As(err, ¬Found):fmt.Printf("Resource not found: %s", notFound.ResourceID)case errors.As(err, &rateLimit):fmt.Printf("Rate limited. Retry after %v", rateLimit.RetryAfter)time.Sleep(rateLimit.RetryAfter)case errors.As(err, &authErr):fmt.Println("Invalid or expired API key")case errors.As(err, &apiErr):fmt.Printf("API error %d: %s - %s", apiErr.StatusCode, apiErr.Code, apiErr.Message)default:fmt.Printf("Unexpected error: %v", err)}}// Error types:// *rekall.APIError - Base type for all API errors// *rekall.AuthError - 401 invalid or expired API key// *rekall.ForbiddenError - 403 insufficient permissions// *rekall.NotFoundError - 404 resource not found// *rekall.ValidationError - 422 invalid input// *rekall.RateLimitError - 429 rate limit exceeded (includes RetryAfter)// *rekall.ServerError - 500+ server error (auto-retried)
Struct Types
All request and response types are exported as Go structs with JSON tags.
// Memory represents a stored memorytype Memory struct {ID string `json:"id"`Type MemoryType `json:"type"`Content string `json:"content"`Context MemoryContext `json:"context"`Importance float64 `json:"importance"`Metadata map[string]interface{} `json:"metadata"`Entities []Entity `json:"entities"`Relationships []Relationship `json:"relationships"`CreatedAt time.Time `json:"createdAt"`UpdatedAt time.Time `json:"updatedAt"`AccessedAt time.Time `json:"accessedAt"`AccessCount int `json:"accessCount"`DecayFactor float64 `json:"decayFactor"`SyncStatus string `json:"syncStatus"`}// MemoryType is an enum for memory typestype MemoryType stringconst (Episodic MemoryType = "episodic"Semantic MemoryType = "semantic"Procedural MemoryType = "procedural"LongTerm MemoryType = "long_term"ShortTerm MemoryType = "short_term"Execution MemoryType = "execution"Preferences MemoryType = "preferences")// MemoryContext determines the memory scopetype MemoryContext stringconst (AgentContext MemoryContext = "agent"UserContext MemoryContext = "user"HiveContext MemoryContext = "hive")
type Entity struct {ID string `json:"id"`Name string `json:"name"`Type string `json:"type"`Attributes map[string]interface{} `json:"attributes"`CreatedAt time.Time `json:"createdAt"`UpdatedAt time.Time `json:"updatedAt"`}type Relationship struct {ID string `json:"id"`SourceID string `json:"sourceId"`TargetID string `json:"targetId"`Type string `json:"type"`Attributes map[string]interface{} `json:"attributes"`CreatedAt time.Time `json:"createdAt"`}type SearchResult struct {Memory Memory `json:"memory"`Score float64 `json:"score"`Source string `json:"source"` // "local" or "cloud"}// SyncStrategy for conflict resolutiontype SyncStrategy stringconst (LastWriteWins SyncStrategy = "last-write-wins"CloudWins SyncStrategy = "cloud-wins"LocalWins SyncStrategy = "local-wins"Manual SyncStrategy = "manual")
Builder Patterns
For complex queries, use the fluent builder API as an alternative to struct literals.
results, err := agent.Memories.Search(ctx, rekall.NewSearchBuilder().Query("customer onboarding issues").Type(rekall.Episodic).Context(rekall.AgentContext).Limit(10).Threshold(0.7).CreatedAfter(time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC)).TagContains("support").SortBy(rekall.SortByRelevance).Build(),)
memory, err := agent.Memories.Create(ctx, rekall.NewMemoryBuilder().Type(rekall.Episodic).Content("User completed onboarding flow.").Context(rekall.UserContext).Importance(0.7).Tag("onboarding").Tag("success").Meta("userId", "user_123").Meta("step", "complete").Build(),)
// Functional options for agent constructionagent, err := rekall.NewAgent(rekall.WithAPIKey(apiKey),rekall.WithAgentID("my-assistant"),rekall.WithStoragePath("./rekall.db"),rekall.WithSyncEnabled(true),rekall.WithSyncInterval(30 * time.Second),rekall.WithSyncStrategy(rekall.LastWriteWins),rekall.WithMaxHTTPConns(20),rekall.WithMaxDBConns(5),rekall.WithRequestTimeout(30 * time.Second),rekall.WithRetries(3),rekall.WithLogger(slog.Default()),)
Ptr helper
Use rekall.Ptr(value) to convert any value to a pointer. This is useful for optional fields in struct literals, such as Type: rekall.Ptr(rekall.Episodic).
