Go SDK
A Go client for twin memory is planned. Until it ships, a small client over net/http does the same job.
A Go SDK is coming Soon. It is not published yet, and nothing on this page can be installed today. The API is plain HTTP and JSON, so the client below works now with the standard library alone.
Planned shape
This is the shape we plan, so you can see where it is going. It may change before release.
client := twinmemory.New(os.Getenv("TWIN_MEMORY_API_KEY"))
_, err := client.Add(ctx, "user_42", []twinmemory.Item{{ID: "msg-001", Session: "onboarding", Role: "user", Text: "I just moved to Lisbon."}})
brief, err := client.Search(ctx, "user_42", "Where does Alice live?")
receipt, err := client.Forget(ctx, "user_42", twinmemory.Forget{Items: []string{"msg-001"}})The end user is an argument of every call, as it is a header of every request.
Use plain HTTP today
package twin
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const base = "https://api.twinmemory.dev/v1/memory/"
// Call posts body to one route for one end user and decodes the answer into out.
// It reports found=false on a 404: nothing was ever written for this end user.
func Call(ctx context.Context, user, route string, body, out any) (found bool, err error) {
payload, err := json.Marshal(body)
if err != nil {
return false, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, base+route, bytes.NewReader(payload))
if err != nil {
return false, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TWIN_MEMORY_API_KEY"))
req.Header.Set("X-Twin-Account", user)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return false, err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound && route != "correct" {
return false, nil
}
if res.StatusCode >= 300 {
detail, _ := io.ReadAll(res.Body)
return false, fmt.Errorf("%s: %d %s", route, res.StatusCode, detail)
}
return true, json.NewDecoder(res.Body).Decode(out)
}type Item map[string]any
var added map[string]any
_, err := twin.Call(ctx, "user_42", "add", map[string]any{"items": []Item{{
"id": "msg-001", "session": "onboarding", "role": "user",
"text": "I just moved to Lisbon from Berlin for the new job at Acme.",
}}}, &added)
var brief struct {
Known []string `json:"known"`
Facts []struct{ ID, Text, Source string } `json:"facts"`
Hits []struct{ ID, Path, Text string } `json:"hits"`
Held *bool `json:"held"`
}
found, err := twin.Call(ctx, "user_42", "search", map[string]any{"query": "Where does Alice live?"}, &brief)
var receipt map[string]any
_, err = twin.Call(ctx, "user_42", "forget", map[string]any{"items": []string{"msg-001"}}, &receipt)foundisfalsewhen the end user has no memory yet. Treat it as an empty result.Heldis a pointer because the field is absent when no judgement was made.- Errors carry
{"detail": ...}. See Errors.
Every other route has the same shape: a POST to /v1/memory/<name> with a JSON body. See the
API reference.