feat: add Go client SDK with ogen code generation (#375)

Add a Go client for the Hindsight API using ogen for strongly-typed code
generation from the OpenAPI 3.1 spec. The client provides a high-level
wrapper with functional options around the generated code, covering all
core operations (retain, recall, reflect, bank management).

Includes:
- ogen-based code generation with OpenAPI 3.1 spec preprocessing
- High-level Client wrapper with idiomatic Go API
- Functional options for all operations (WithBudget, WithTags, etc.)
- OgenClient() escape hatch for advanced operations
- Integration tests and godoc examples
- Go SDK reference docs and cookbook entries (quickstart, concurrent
  pipeline, memory-augmented API service)
- Updated generate-clients.sh with Go generation step

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eliah Rusin 2026-02-16 13:35:40 +03:00 committed by GitHub
parent b4b5c44a87
commit 2a47389f2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 31851 additions and 5 deletions

View file

@ -0,0 +1,178 @@
# Hindsight Go Client
Go client for the [Hindsight](https://github.com/vectorize-io/hindsight) agent memory API.
## Installation
```bash
go get github.com/vectorize-io/hindsight-client-go
```
Requires Go 1.25+.
## Quick Start
```go
package main
import (
"context"
"fmt"
"log"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Store a memory
_, err = client.Retain(ctx, "my-bank", "The user prefers dark mode")
if err != nil {
log.Fatal(err)
}
// Recall memories
resp, err := client.Recall(ctx, "my-bank", "What are the user's preferences?")
if err != nil {
log.Fatal(err)
}
for _, r := range resp.Results {
fmt.Println(r.Text)
}
// Reflect with reasoning
ref, err := client.Reflect(ctx, "my-bank", "Summarize what you know about the user")
if err != nil {
log.Fatal(err)
}
fmt.Println(ref.Text)
}
```
## Authentication
```go
client, err := hindsight.New("http://localhost:8888", hindsight.WithAPIKey("your-key"))
```
## Core Operations
### Retain (Store Memories)
```go
// Single memory
_, err := client.Retain(ctx, "bank-id", "Alice loves Python",
hindsight.WithContext("programming discussion"),
hindsight.WithTags([]string{"tech"}),
hindsight.WithDocumentID("conv-123"),
)
// Batch
items := []hindsight.MemoryItem{
{Content: "First memory"},
{Content: "Second memory"},
}
_, err := client.RetainBatch(ctx, "bank-id", items,
hindsight.WithDocumentTags([]string{"import"}),
hindsight.WithAsync(true),
)
```
### Recall (Retrieve Memories)
```go
resp, err := client.Recall(ctx, "bank-id", "What does Alice like?",
hindsight.WithBudget(hindsight.BudgetHigh),
hindsight.WithMaxTokens(4096),
hindsight.WithTypes([]string{"world", "experience"}),
hindsight.WithTrace(true),
hindsight.WithRecallTags([]string{"tech"}),
)
for _, r := range resp.Results {
fmt.Printf("[%s] %s\n", r.Type.Or("unknown"), r.Text)
}
```
### Reflect (Reason with Memories)
```go
resp, err := client.Reflect(ctx, "bank-id", "What are the user's interests?",
hindsight.WithReflectBudget(hindsight.BudgetMid),
hindsight.WithReflectMaxTokens(2048),
hindsight.WithResponseSchema(map[string]any{
"type": "object",
"properties": map[string]any{
"interests": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
},
}),
)
fmt.Println(resp.Text)
```
### Bank Management
```go
// Create bank with personality
_, err := client.CreateBank(ctx, "my-bank",
hindsight.WithBankName("My Agent"),
hindsight.WithMission("Help users with coding tasks"),
hindsight.WithDisposition(hindsight.DispositionTraits{
Skepticism: 3,
Literalism: 2,
Empathy: 4,
}),
)
// Update mission
_, err = client.SetMission(ctx, "my-bank", "New mission statement")
// List all banks
banks, err := client.ListBanks(ctx)
// Delete bank
err = client.DeleteBank(ctx, "my-bank")
```
## Advanced Usage
For operations not covered by the high-level wrapper (documents, entities, operations, mental models, directives), access the ogen-generated client directly:
```go
ogen := client.OgenClient()
// List entities
resp, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{
BankID: "my-bank",
})
// Create mental model
resp, err := ogen.CreateMentalModel(ctx, &ogenapi.CreateMentalModelRequest{
Name: "user-preferences",
SourceQuery: "What are the user's preferences?",
}, ogenapi.CreateMentalModelParams{BankID: "my-bank"})
```
## Code Generation
The client is built on [ogen](https://github.com/ogen-go/ogen), generating strongly-typed Go code from the OpenAPI 3.1 spec. To regenerate after API changes:
```bash
cd hindsight-clients/go
go generate ./...
```
## Running Tests
Integration tests require a running Hindsight API server:
```bash
HINDSIGHT_API_URL=http://localhost:8888 go test -v -tags=integration ./...
```

View file

@ -0,0 +1,118 @@
package hindsight
import (
"context"
"fmt"
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
)
// CreateBank creates a new memory bank or updates an existing one.
func (c *Client) CreateBank(ctx context.Context, bankID string, opts ...CreateBankOption) (*BankProfileResponse, error) {
var cfg createBankConfig
for _, o := range opts {
o(&cfg)
}
req := &ogenapi.CreateBankRequest{}
if cfg.name != nil {
req.Name = ogenapi.NewOptString(*cfg.name)
}
if cfg.mission != nil {
req.Mission = ogenapi.NewOptString(*cfg.mission)
}
if cfg.disposition != nil {
req.Disposition = ogenapi.NewOptDispositionTraits(*cfg.disposition)
}
res, err := c.api.CreateOrUpdateBank(ctx, req, ogenapi.CreateOrUpdateBankParams{
BankID: bankID,
})
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.BankProfileResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}
// GetBankProfile retrieves the profile for a memory bank.
func (c *Client) GetBankProfile(ctx context.Context, bankID string) (*BankProfileResponse, error) {
res, err := c.api.GetBankProfile(ctx, ogenapi.GetBankProfileParams{
BankID: bankID,
})
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.BankProfileResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}
// ListBanks returns all memory banks.
func (c *Client) ListBanks(ctx context.Context) (*BankListResponse, error) {
res, err := c.api.ListBanks(ctx)
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.BankListResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}
// DeleteBank permanently deletes a memory bank and all its data.
func (c *Client) DeleteBank(ctx context.Context, bankID string) error {
_, err := c.api.DeleteBank(ctx, ogenapi.DeleteBankParams{
BankID: bankID,
})
return err
}
// SetMission updates the mission for a memory bank.
func (c *Client) SetMission(ctx context.Context, bankID, mission string) (*BankProfileResponse, error) {
req := &ogenapi.CreateBankRequest{
Mission: ogenapi.NewOptString(mission),
}
res, err := c.api.CreateOrUpdateBank(ctx, req, ogenapi.CreateOrUpdateBankParams{
BankID: bankID,
})
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.BankProfileResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}
// UpdateDisposition updates the personality traits for a memory bank.
func (c *Client) UpdateDisposition(ctx context.Context, bankID string, traits DispositionTraits) (*BankProfileResponse, error) {
req := &ogenapi.UpdateDispositionRequest{
Disposition: traits,
}
res, err := c.api.UpdateBankDisposition(ctx, req, ogenapi.UpdateBankDispositionParams{
BankID: bankID,
})
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.BankProfileResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}

View file

@ -0,0 +1,40 @@
// Package hindsight provides a Go client for the Hindsight agent memory API.
//
// Hindsight is a long-term memory system for AI agents. This client wraps the
// auto-generated ogen API client with a simpler, Go-idiomatic interface for the
// core operations: retain (store), recall (retrieve), and reflect (reason).
//
// # Quick Start
//
// client, err := hindsight.New("http://localhost:8888")
// if err != nil {
// log.Fatal(err)
// }
//
// // Store a memory
// _, err = client.Retain(ctx, "my-bank", "The user prefers dark mode")
//
// // Recall memories
// resp, err := client.Recall(ctx, "my-bank", "What are the user's preferences?")
// for _, r := range resp.Results {
// fmt.Println(r.Text)
// }
//
// // Reflect with reasoning
// ref, err := client.Reflect(ctx, "my-bank", "Summarize what you know about the user")
// fmt.Println(ref.Text)
//
// # Authentication
//
// For authenticated deployments, pass an API key:
//
// client, err := hindsight.New("http://localhost:8888", hindsight.WithAPIKey("your-key"))
//
// # Advanced Usage
//
// For operations not covered by the high-level wrapper (documents, entities,
// operations, mental models, directives), access the ogen-generated client:
//
// ogen := client.OgenClient()
// resp, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{BankID: "my-bank"})
package hindsight

View file

@ -0,0 +1,124 @@
package hindsight_test
import (
"context"
"fmt"
"log"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func Example() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Store a memory
_, err = client.Retain(ctx, "my-bank", "The user prefers dark mode")
if err != nil {
log.Fatal(err)
}
// Recall memories
resp, err := client.Recall(ctx, "my-bank", "What are the user's preferences?")
if err != nil {
log.Fatal(err)
}
for _, r := range resp.Results {
fmt.Println(r.Text)
}
// Reflect with reasoning
ref, err := client.Reflect(ctx, "my-bank", "Summarize what you know about the user")
if err != nil {
log.Fatal(err)
}
fmt.Println(ref.Text)
}
func ExampleNew_withAPIKey() {
_, err := hindsight.New(
"http://localhost:8888",
hindsight.WithAPIKey("your-api-key"),
)
if err != nil {
log.Fatal(err)
}
}
func ExampleClient_Retain() {
client, _ := hindsight.New("http://localhost:8888")
ctx := context.Background()
// Simple retain
_, _ = client.Retain(ctx, "my-bank", "Alice loves Python programming")
// Retain with options
_, _ = client.Retain(ctx, "my-bank", "Bob went hiking",
hindsight.WithContext("outdoor activities"),
hindsight.WithTags([]string{"hobbies"}),
hindsight.WithDocumentID("conversation-123"),
)
}
func ExampleClient_RetainBatch() {
client, _ := hindsight.New("http://localhost:8888")
ctx := context.Background()
items := []hindsight.MemoryItem{
{Content: "Alice completed the project"},
{Content: "Bob started learning Go"},
{Content: "Charlie presented at the conference"},
}
_, _ = client.RetainBatch(ctx, "my-bank", items,
hindsight.WithDocumentTags([]string{"team-updates"}),
)
}
func ExampleClient_Recall() {
client, _ := hindsight.New("http://localhost:8888")
ctx := context.Background()
resp, _ := client.Recall(ctx, "my-bank", "What does Alice like?",
hindsight.WithBudget(hindsight.BudgetHigh),
hindsight.WithMaxTokens(4096),
hindsight.WithTypes([]string{"world", "experience"}),
hindsight.WithTrace(true),
)
for _, r := range resp.Results {
fmt.Printf("[%s] %s\n", r.Type.Or("unknown"), r.Text)
}
}
func ExampleClient_Reflect() {
client, _ := hindsight.New("http://localhost:8888")
ctx := context.Background()
resp, _ := client.Reflect(ctx, "my-bank",
"What are the user's professional interests?",
hindsight.WithReflectBudget(hindsight.BudgetMid),
hindsight.WithReflectMaxTokens(2048),
)
fmt.Println(resp.Text)
}
func ExampleClient_CreateBank() {
client, _ := hindsight.New("http://localhost:8888")
ctx := context.Background()
_, _ = client.CreateBank(ctx, "my-bank",
hindsight.WithBankName("My Agent"),
hindsight.WithMission("Help users with programming tasks"),
hindsight.WithDisposition(hindsight.DispositionTraits{
Skepticism: 3,
Literalism: 2,
Empathy: 4,
}),
)
}

View file

@ -0,0 +1,4 @@
package hindsight
//go:generate go run ./internal/cmd/preprocess ../../hindsight-docs/static/openapi.json internal/ogenapi/openapi.json
//go:generate go run github.com/ogen-go/ogen/cmd/ogen --target internal/ogenapi -package ogenapi --clean --config ogen.yml internal/ogenapi/openapi.json

View file

@ -0,0 +1,29 @@
module github.com/vectorize-io/hindsight-client-go
go 1.25.0
require (
github.com/go-faster/errors v0.7.1
github.com/go-faster/jx v1.2.0
github.com/ogen-go/ogen v1.19.0
)
require (
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-faster/yaml v0.4.6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

View file

@ -0,0 +1,60 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI=
github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE=
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ogen-go/ogen v1.19.0 h1:YvdNpeQJ8A8dLLpS6Vs4WxXL53BT6tBPxH0VSjfALhA=
github.com/ogen-go/ogen v1.19.0/go.mod h1:DeShwO+TEpLYXNCuZliSAedphphXsJaTGGbmSomWUjE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,66 @@
package hindsight
import (
"net/http"
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
)
// Client is the high-level Hindsight API client. It wraps the ogen-generated
// client with convenience methods for core operations.
type Client struct {
api *ogenapi.Client
}
// New creates a new Hindsight client for the given base URL.
//
// By default, no authentication is configured. Use [WithAPIKey] to set a
// Bearer token, or [WithHTTPClient] for full control over the HTTP transport.
func New(baseURL string, opts ...Option) (*Client, error) {
cfg := clientConfig{}
for _, o := range opts {
o(&cfg)
}
var httpClient http.Client
if cfg.httpClient != nil {
httpClient = *cfg.httpClient
}
if cfg.apiKey != "" {
base := httpClient.Transport
if base == nil {
base = http.DefaultTransport
}
httpClient.Transport = &authTransport{
base: base,
token: cfg.apiKey,
}
}
api, err := ogenapi.NewClient(baseURL, ogenapi.WithClient(&httpClient))
if err != nil {
return nil, err
}
return &Client{api: api}, nil
}
// OgenClient returns the underlying ogen-generated client for advanced
// operations not covered by the high-level wrapper (documents, entities,
// operations, mental models, directives).
func (c *Client) OgenClient() *ogenapi.Client {
return c.api
}
// authTransport injects a Bearer token into every request.
type authTransport struct {
base http.RoundTripper
token string
}
func (t *authTransport) RoundTrip(r *http.Request) (*http.Response, error) {
r = r.Clone(r.Context())
r.Header.Set("Authorization", "Bearer "+t.token)
return t.base.RoundTrip(r)
}

View file

@ -0,0 +1,399 @@
//go:build integration
package hindsight_test
import (
"context"
"fmt"
"os"
"testing"
"time"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func apiURL(t *testing.T) string {
t.Helper()
u := os.Getenv("HINDSIGHT_API_URL")
if u == "" {
u = "http://localhost:8888"
}
return u
}
func newClient(t *testing.T) *hindsight.Client {
t.Helper()
c, err := hindsight.New(apiURL(t))
if err != nil {
t.Fatal(err)
}
return c
}
func uniqueBank(t *testing.T) string {
t.Helper()
return fmt.Sprintf("go_test_%d", time.Now().UnixNano())
}
// --- Retain tests ---
func TestRetainSingle(t *testing.T) {
c := newClient(t)
ctx := context.Background()
resp, err := c.Retain(ctx, uniqueBank(t), "Alice loves artificial intelligence and machine learning")
if err != nil {
t.Fatal(err)
}
if !resp.Success {
t.Error("expected success=true")
}
}
func TestRetainWithContext(t *testing.T) {
c := newClient(t)
ctx := context.Background()
resp, err := c.Retain(ctx, uniqueBank(t), "Bob went hiking in the mountains",
hindsight.WithTimestamp(time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)),
hindsight.WithContext("outdoor activities"),
)
if err != nil {
t.Fatal(err)
}
if !resp.Success {
t.Error("expected success=true")
}
}
func TestRetainBatch(t *testing.T) {
c := newClient(t)
ctx := context.Background()
items := []hindsight.MemoryItem{
{Content: "Charlie enjoys reading science fiction books"},
{Content: "Diana is learning to play the guitar"},
{Content: "Eve completed a marathon last month"},
}
resp, err := c.RetainBatch(ctx, uniqueBank(t), items)
if err != nil {
t.Fatal(err)
}
if !resp.Success {
t.Error("expected success=true")
}
if resp.ItemsCount != 3 {
t.Errorf("expected items_count=3, got %d", resp.ItemsCount)
}
}
func TestRetainWithTags(t *testing.T) {
c := newClient(t)
ctx := context.Background()
resp, err := c.Retain(ctx, uniqueBank(t), "New feature implementation for project Z",
hindsight.WithTags([]string{"project_z", "features"}),
)
if err != nil {
t.Fatal(err)
}
if !resp.Success {
t.Error("expected success=true")
}
}
func TestRetainBatchWithDocumentTags(t *testing.T) {
c := newClient(t)
ctx := context.Background()
items := []hindsight.MemoryItem{
{Content: "First item in batch"},
{Content: "Second item in batch"},
}
resp, err := c.RetainBatch(ctx, uniqueBank(t), items,
hindsight.WithDocumentTags([]string{"batch_import", "test_data"}),
)
if err != nil {
t.Fatal(err)
}
if !resp.Success {
t.Error("expected success=true")
}
if resp.ItemsCount != 2 {
t.Errorf("expected items_count=2, got %d", resp.ItemsCount)
}
}
// --- Recall tests ---
func setupRecallBank(t *testing.T, c *hindsight.Client, bankID string) {
t.Helper()
ctx := context.Background()
items := []hindsight.MemoryItem{
{Content: "Alice loves programming in Python"},
{Content: "Bob enjoys hiking and outdoor adventures"},
{Content: "Charlie is interested in quantum physics"},
{Content: "Diana plays the violin beautifully"},
}
_, err := c.RetainBatch(ctx, bankID, items)
if err != nil {
t.Fatal(err)
}
}
func TestRecallBasic(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
setupRecallBank(t, c, bankID)
resp, err := c.Recall(ctx, bankID, "What does Alice like?")
if err != nil {
t.Fatal(err)
}
if len(resp.Results) == 0 {
t.Error("expected at least one result")
}
found := false
for _, r := range resp.Results {
if contains(r.Text, "Alice") || contains(r.Text, "Python") || contains(r.Text, "programming") {
found = true
break
}
}
if !found {
t.Error("expected a result mentioning Alice or Python")
}
}
func TestRecallWithMaxTokens(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
setupRecallBank(t, c, bankID)
resp, err := c.Recall(ctx, bankID, "outdoor activities",
hindsight.WithMaxTokens(1024),
)
if err != nil {
t.Fatal(err)
}
if resp.Results == nil {
t.Error("expected results, got nil")
}
}
func TestRecallFullFeatured(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
setupRecallBank(t, c, bankID)
resp, err := c.Recall(ctx, bankID, "What are people's hobbies?",
hindsight.WithTypes([]string{"world"}),
hindsight.WithMaxTokens(2048),
hindsight.WithTrace(true),
)
if err != nil {
t.Fatal(err)
}
if resp.Results == nil {
t.Error("expected results, got nil")
}
}
// --- Reflect tests ---
func setupReflectBank(t *testing.T, c *hindsight.Client, bankID string) {
t.Helper()
ctx := context.Background()
_, err := c.CreateBank(ctx, bankID,
hindsight.WithMission("I am a helpful AI assistant interested in technology and science."),
)
if err != nil {
t.Fatal(err)
}
items := []hindsight.MemoryItem{
{Content: "The Python programming language is great for data science"},
{Content: "Machine learning models can recognize patterns in data"},
{Content: "Neural networks are inspired by biological neurons"},
}
_, err = c.RetainBatch(ctx, bankID, items)
if err != nil {
t.Fatal(err)
}
}
func TestReflectBasic(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
setupReflectBank(t, c, bankID)
resp, err := c.Reflect(ctx, bankID, "What do you think about artificial intelligence?")
if err != nil {
t.Fatal(err)
}
if resp.Text == "" {
t.Error("expected non-empty response text")
}
}
func TestReflectWithMaxTokens(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
setupReflectBank(t, c, bankID)
resp, err := c.Reflect(ctx, bankID, "What do you think about Python?",
hindsight.WithReflectMaxTokens(500),
)
if err != nil {
t.Fatal(err)
}
if resp.Text == "" {
t.Error("expected non-empty response text")
}
}
// --- Bank tests ---
func TestCreateBank(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
resp, err := c.CreateBank(ctx, bankID,
hindsight.WithBankName("Test Bank"),
hindsight.WithMission("A test bank for Go client"),
)
if err != nil {
t.Fatal(err)
}
if resp.BankID != bankID {
t.Errorf("expected bank_id=%q, got %q", bankID, resp.BankID)
}
}
func TestSetMission(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
resp, err := c.SetMission(ctx, bankID, "Be a helpful PM tracking sprint progress")
if err != nil {
t.Fatal(err)
}
if resp.BankID != bankID {
t.Errorf("expected bank_id=%q, got %q", bankID, resp.BankID)
}
if resp.Mission != "Be a helpful PM tracking sprint progress" {
t.Errorf("expected mission=%q, got %q", "Be a helpful PM tracking sprint progress", resp.Mission)
}
}
func TestListBanks(t *testing.T) {
c := newClient(t)
ctx := context.Background()
// Create a bank first
bankID := uniqueBank(t)
_, err := c.CreateBank(ctx, bankID)
if err != nil {
t.Fatal(err)
}
resp, err := c.ListBanks(ctx)
if err != nil {
t.Fatal(err)
}
if len(resp.Banks) == 0 {
t.Error("expected at least one bank")
}
}
func TestDeleteBank(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
_, err := c.CreateBank(ctx, bankID, hindsight.WithMission("will be deleted"))
if err != nil {
t.Fatal(err)
}
err = c.DeleteBank(ctx, bankID)
if err != nil {
t.Fatal(err)
}
}
// --- End-to-end workflow ---
func TestCompleteWorkflow(t *testing.T) {
c := newClient(t)
ctx := context.Background()
bankID := uniqueBank(t)
// 1. Create bank
_, err := c.CreateBank(ctx, bankID,
hindsight.WithMission("I am a software engineer who loves Python programming."),
)
if err != nil {
t.Fatal(err)
}
// 2. Store memories
items := []hindsight.MemoryItem{
{Content: "I completed a project using FastAPI"},
{Content: "I learned about async programming in Python"},
{Content: "I enjoy working on open source projects"},
}
storeResp, err := c.RetainBatch(ctx, bankID, items)
if err != nil {
t.Fatal(err)
}
if !storeResp.Success {
t.Error("expected retain success")
}
// 3. Search for relevant memories
recallResp, err := c.Recall(ctx, bankID, "What programming technologies do I use?")
if err != nil {
t.Fatal(err)
}
if len(recallResp.Results) == 0 {
t.Error("expected recall results")
}
// 4. Generate contextual answer
reflectResp, err := c.Reflect(ctx, bankID, "What are my professional interests?")
if err != nil {
t.Fatal(err)
}
if reflectResp.Text == "" {
t.Error("expected non-empty reflect response")
}
}
// contains checks if s contains substr (case-sensitive).
func contains(s, substr string) bool {
return len(s) >= len(substr) && searchString(s, substr)
}
func searchString(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}

View file

@ -0,0 +1,111 @@
// Command preprocess converts an OpenAPI 3.1 spec to be ogen-compatible.
//
// Hindsight's OpenAPI spec uses anyOf: [{type: T}, {type: null}] for optional
// fields. ogen cannot handle the null type in header/query parameter schemas
// with style:simple. This tool rewrites such patterns to plain {type: T},
// making the spec consumable by ogen while preserving semantic meaning (the
// fields are already marked as required: false).
package main
import (
"encoding/json"
"fmt"
"os"
)
func main() {
if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "usage: preprocess <input.json> <output.json>\n")
os.Exit(1)
}
data, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "read: %v\n", err)
os.Exit(1)
}
var spec map[string]any
if err := json.Unmarshal(data, &spec); err != nil {
fmt.Fprintf(os.Stderr, "parse: %v\n", err)
os.Exit(1)
}
convertAnyOfNull(spec)
out, err := json.MarshalIndent(spec, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "marshal: %v\n", err)
os.Exit(1)
}
if err := os.WriteFile(os.Args[2], out, 0o644); err != nil {
fmt.Fprintf(os.Stderr, "write: %v\n", err)
os.Exit(1)
}
}
// convertAnyOfNull recursively walks the spec and converts
// anyOf: [{type: T}, {type: null}] → {type: T} (or just the non-null schema).
// For component schema properties, it also does the conversion but additionally
// handles cases where the non-null branch is a $ref.
func convertAnyOfNull(v any) {
switch val := v.(type) {
case map[string]any:
// Check if this object has an "anyOf" with exactly a non-null + null pair.
if tryConvertAnyOf(val) {
// Converted in place; recurse into the result.
convertAnyOfNull(val)
return
}
// Recurse into all values.
for _, child := range val {
convertAnyOfNull(child)
}
case []any:
for _, child := range val {
convertAnyOfNull(child)
}
}
}
// tryConvertAnyOf checks if m has anyOf: [{...}, {type: null}] and converts
// it in-place. Returns true if conversion happened.
func tryConvertAnyOf(m map[string]any) bool {
anyOf, ok := m["anyOf"].([]any)
if !ok || len(anyOf) != 2 {
return false
}
// Identify which branch is null and which is the real type.
var realIdx int = -1
for i, branch := range anyOf {
branchMap, ok := branch.(map[string]any)
if !ok {
return false
}
if branchMap["type"] == "null" {
continue
}
realIdx = i
}
if realIdx == -1 {
return false // Both are null? Skip.
}
realBranch, ok := anyOf[realIdx].(map[string]any)
if !ok {
return false
}
// Remove the anyOf key.
delete(m, "anyOf")
// Copy all properties from the real branch into the parent.
for k, v := range realBranch {
m[k] = v
}
return true
}

View file

@ -0,0 +1,2 @@
# Preprocessed OpenAPI spec (intermediate artifact, regenerated by go generate)
openapi.json

View file

@ -0,0 +1,61 @@
// Code generated by ogen, DO NOT EDIT.
package ogenapi
import (
"net/http"
ht "github.com/ogen-go/ogen/http"
)
type (
optionFunc[C any] func(*C)
)
type clientConfig struct {
Client ht.Client
}
// ClientOption is client config option.
type ClientOption interface {
applyClient(*clientConfig)
}
var _ ClientOption = (optionFunc[clientConfig])(nil)
func (o optionFunc[C]) applyClient(c *C) {
o(c)
}
func newClientConfig(opts ...ClientOption) clientConfig {
cfg := clientConfig{
Client: http.DefaultClient,
}
for _, opt := range opts {
opt.applyClient(&cfg)
}
return cfg
}
type baseClient struct {
cfg clientConfig
}
func (cfg clientConfig) baseClient() (c baseClient, err error) {
c = baseClient{cfg: cfg}
return c, nil
}
// Option is config option.
type Option interface {
ClientOption
}
// WithClient specifies http client to use.
func WithClient(client ht.Client) ClientOption {
return optionFunc[clientConfig](func(cfg *clientConfig) {
if client != nil {
cfg.Client = client
}
})
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,179 @@
// Code generated by ogen, DO NOT EDIT.
package ogenapi
// setDefaults set default value of fields.
func (s *AddBackgroundRequest) setDefaults() {
{
val := bool(true)
s.UpdateDisposition.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *BankStatsResponse) setDefaults() {
{
val := int(0)
s.PendingConsolidation.SetTo(val)
}
{
val := int(0)
s.TotalObservations.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *ChunkData) setDefaults() {
{
val := bool(false)
s.Truncated.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *ChunkIncludeOptions) setDefaults() {
{
val := int(8192)
s.MaxTokens.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *ConsolidationResponse) setDefaults() {
{
val := bool(false)
s.Deduplicated.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *CreateDirectiveRequest) setDefaults() {
{
val := bool(true)
s.IsActive.SetTo(val)
}
{
val := int(0)
s.Priority.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *CreateMentalModelRequest) setDefaults() {
{
val := int(2048)
s.MaxTokens.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *DirectiveResponse) setDefaults() {
{
val := bool(true)
s.IsActive.SetTo(val)
}
{
val := int(0)
s.Priority.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *EntityIncludeOptions) setDefaults() {
{
val := int(500)
s.MaxTokens.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *MentalModelResponse) setDefaults() {
{
val := int(2048)
s.MaxTokens.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *MentalModelTrigger) setDefaults() {
{
val := bool(false)
s.RefreshAfterConsolidation.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *RecallRequest) setDefaults() {
{
val := Budget("low")
s.Budget.SetTo(val)
}
{
val := int(4096)
s.MaxTokens.SetTo(val)
}
{
val := RecallRequestTagsMatch("any")
s.TagsMatch.SetTo(val)
}
{
val := bool(false)
s.Trace.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *ReflectRequest) setDefaults() {
{
val := Budget("low")
s.Budget.SetTo(val)
}
{
val := int(4096)
s.MaxTokens.SetTo(val)
}
{
val := ReflectRequestTagsMatch("any")
s.TagsMatch.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *ReflectToolCall) setDefaults() {
{
val := int(0)
s.Iteration.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *RetainRequest) setDefaults() {
{
val := bool(false)
s.Async.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *TokenUsage) setDefaults() {
{
val := int(0)
s.InputTokens.SetTo(val)
}
{
val := int(0)
s.OutputTokens.SetTo(val)
}
{
val := int(0)
s.TotalTokens.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *ToolCallsIncludeOptions) setDefaults() {
{
val := bool(true)
s.Output.SetTo(val)
}
}

View file

@ -0,0 +1,170 @@
// Code generated by ogen, DO NOT EDIT.
package ogenapi
type AddBankBackgroundRes interface {
addBankBackgroundRes()
}
type CancelOperationRes interface {
cancelOperationRes()
}
type ClearBankMemoriesRes interface {
clearBankMemoriesRes()
}
type ClearObservationsRes interface {
clearObservationsRes()
}
type CreateDirectiveRes interface {
createDirectiveRes()
}
type CreateMentalModelRes interface {
createMentalModelRes()
}
type CreateOrUpdateBankRes interface {
createOrUpdateBankRes()
}
type DeleteBankRes interface {
deleteBankRes()
}
type DeleteDirectiveRes interface {
deleteDirectiveRes()
}
type DeleteDocumentRes interface {
deleteDocumentRes()
}
type DeleteMentalModelRes interface {
deleteMentalModelRes()
}
type GetAgentStatsRes interface {
getAgentStatsRes()
}
type GetBankConfigRes interface {
getBankConfigRes()
}
type GetBankProfileRes interface {
getBankProfileRes()
}
type GetChunkRes interface {
getChunkRes()
}
type GetDirectiveRes interface {
getDirectiveRes()
}
type GetDocumentRes interface {
getDocumentRes()
}
type GetEntityRes interface {
getEntityRes()
}
type GetGraphRes interface {
getGraphRes()
}
type GetMemoryRes interface {
getMemoryRes()
}
type GetMentalModelRes interface {
getMentalModelRes()
}
type GetOperationStatusRes interface {
getOperationStatusRes()
}
type ListBanksRes interface {
listBanksRes()
}
type ListDirectivesRes interface {
listDirectivesRes()
}
type ListDocumentsRes interface {
listDocumentsRes()
}
type ListEntitiesRes interface {
listEntitiesRes()
}
type ListMemoriesRes interface {
listMemoriesRes()
}
type ListMentalModelsRes interface {
listMentalModelsRes()
}
type ListOperationsRes interface {
listOperationsRes()
}
type ListTagsRes interface {
listTagsRes()
}
type RecallMemoriesRes interface {
recallMemoriesRes()
}
type ReflectRes interface {
reflectRes()
}
type RefreshMentalModelRes interface {
refreshMentalModelRes()
}
type RegenerateEntityObservationsRes interface {
regenerateEntityObservationsRes()
}
type ResetBankConfigRes interface {
resetBankConfigRes()
}
type RetainMemoriesRes interface {
retainMemoriesRes()
}
type TriggerConsolidationRes interface {
triggerConsolidationRes()
}
type UpdateBankConfigRes interface {
updateBankConfigRes()
}
type UpdateBankDispositionRes interface {
updateBankDispositionRes()
}
type UpdateBankRes interface {
updateBankRes()
}
type UpdateDirectiveRes interface {
updateDirectiveRes()
}
type UpdateMentalModelRes interface {
updateMentalModelRes()
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,54 @@
// Code generated by ogen, DO NOT EDIT.
package ogenapi
// OperationName is the ogen operation name
type OperationName = string
const (
AddBankBackgroundOperation OperationName = "AddBankBackground"
CancelOperationOperation OperationName = "CancelOperation"
ClearBankMemoriesOperation OperationName = "ClearBankMemories"
ClearObservationsOperation OperationName = "ClearObservations"
CreateDirectiveOperation OperationName = "CreateDirective"
CreateMentalModelOperation OperationName = "CreateMentalModel"
CreateOrUpdateBankOperation OperationName = "CreateOrUpdateBank"
DeleteBankOperation OperationName = "DeleteBank"
DeleteDirectiveOperation OperationName = "DeleteDirective"
DeleteDocumentOperation OperationName = "DeleteDocument"
DeleteMentalModelOperation OperationName = "DeleteMentalModel"
GetAgentStatsOperation OperationName = "GetAgentStats"
GetBankConfigOperation OperationName = "GetBankConfig"
GetBankProfileOperation OperationName = "GetBankProfile"
GetChunkOperation OperationName = "GetChunk"
GetDirectiveOperation OperationName = "GetDirective"
GetDocumentOperation OperationName = "GetDocument"
GetEntityOperation OperationName = "GetEntity"
GetGraphOperation OperationName = "GetGraph"
GetMemoryOperation OperationName = "GetMemory"
GetMentalModelOperation OperationName = "GetMentalModel"
GetOperationStatusOperation OperationName = "GetOperationStatus"
GetVersionOperation OperationName = "GetVersion"
HealthEndpointHealthGetOperation OperationName = "HealthEndpointHealthGet"
ListBanksOperation OperationName = "ListBanks"
ListDirectivesOperation OperationName = "ListDirectives"
ListDocumentsOperation OperationName = "ListDocuments"
ListEntitiesOperation OperationName = "ListEntities"
ListMemoriesOperation OperationName = "ListMemories"
ListMentalModelsOperation OperationName = "ListMentalModels"
ListOperationsOperation OperationName = "ListOperations"
ListTagsOperation OperationName = "ListTags"
MetricsEndpointMetricsGetOperation OperationName = "MetricsEndpointMetricsGet"
RecallMemoriesOperation OperationName = "RecallMemories"
ReflectOperation OperationName = "Reflect"
RefreshMentalModelOperation OperationName = "RefreshMentalModel"
RegenerateEntityObservationsOperation OperationName = "RegenerateEntityObservations"
ResetBankConfigOperation OperationName = "ResetBankConfig"
RetainMemoriesOperation OperationName = "RetainMemories"
TriggerConsolidationOperation OperationName = "TriggerConsolidation"
UpdateBankOperation OperationName = "UpdateBank"
UpdateBankConfigOperation OperationName = "UpdateBankConfig"
UpdateBankDispositionOperation OperationName = "UpdateBankDisposition"
UpdateDirectiveOperation OperationName = "UpdateDirective"
UpdateMentalModelOperation OperationName = "UpdateMentalModel"
)

View file

@ -0,0 +1,264 @@
// Code generated by ogen, DO NOT EDIT.
package ogenapi
// AddBankBackgroundParams is parameters of add_bank_background operation.
type AddBankBackgroundParams struct {
BankID string
}
// CancelOperationParams is parameters of cancel_operation operation.
type CancelOperationParams struct {
BankID string
OperationID string
}
// ClearBankMemoriesParams is parameters of clear_bank_memories operation.
type ClearBankMemoriesParams struct {
BankID string
// Optional fact type filter (world, experience, opinion).
Type OptString `json:",omitempty,omitzero"`
}
// ClearObservationsParams is parameters of clear_observations operation.
type ClearObservationsParams struct {
BankID string
}
// CreateDirectiveParams is parameters of create_directive operation.
type CreateDirectiveParams struct {
BankID string
}
// CreateMentalModelParams is parameters of create_mental_model operation.
type CreateMentalModelParams struct {
BankID string
}
// CreateOrUpdateBankParams is parameters of create_or_update_bank operation.
type CreateOrUpdateBankParams struct {
BankID string
}
// DeleteBankParams is parameters of delete_bank operation.
type DeleteBankParams struct {
BankID string
}
// DeleteDirectiveParams is parameters of delete_directive operation.
type DeleteDirectiveParams struct {
BankID string
DirectiveID string
}
// DeleteDocumentParams is parameters of delete_document operation.
type DeleteDocumentParams struct {
BankID string
DocumentID string
}
// DeleteMentalModelParams is parameters of delete_mental_model operation.
type DeleteMentalModelParams struct {
BankID string
MentalModelID string
}
// GetAgentStatsParams is parameters of get_agent_stats operation.
type GetAgentStatsParams struct {
BankID string
}
// GetBankConfigParams is parameters of get_bank_config operation.
type GetBankConfigParams struct {
BankID string
}
// GetBankProfileParams is parameters of get_bank_profile operation.
type GetBankProfileParams struct {
BankID string
}
// GetChunkParams is parameters of get_chunk operation.
type GetChunkParams struct {
ChunkID string
}
// GetDirectiveParams is parameters of get_directive operation.
type GetDirectiveParams struct {
BankID string
DirectiveID string
}
// GetDocumentParams is parameters of get_document operation.
type GetDocumentParams struct {
BankID string
DocumentID string
}
// GetEntityParams is parameters of get_entity operation.
type GetEntityParams struct {
BankID string
EntityID string
}
// GetGraphParams is parameters of get_graph operation.
type GetGraphParams struct {
BankID string
Type OptString `json:",omitempty,omitzero"`
Limit OptInt `json:",omitempty,omitzero"`
}
// GetMemoryParams is parameters of get_memory operation.
type GetMemoryParams struct {
BankID string
MemoryID string
}
// GetMentalModelParams is parameters of get_mental_model operation.
type GetMentalModelParams struct {
BankID string
MentalModelID string
}
// GetOperationStatusParams is parameters of get_operation_status operation.
type GetOperationStatusParams struct {
BankID string
OperationID string
}
// ListDirectivesParams is parameters of list_directives operation.
type ListDirectivesParams struct {
BankID string
// Filter by tags.
Tags []string `json:",omitempty"`
// How to match tags.
TagsMatch OptListDirectivesTagsMatch `json:",omitempty,omitzero"`
// Only return active directives.
ActiveOnly OptBool `json:",omitempty,omitzero"`
Limit OptInt `json:",omitempty,omitzero"`
Offset OptInt `json:",omitempty,omitzero"`
}
// ListDocumentsParams is parameters of list_documents operation.
type ListDocumentsParams struct {
BankID string
Q OptString `json:",omitempty,omitzero"`
Limit OptInt `json:",omitempty,omitzero"`
Offset OptInt `json:",omitempty,omitzero"`
}
// ListEntitiesParams is parameters of list_entities operation.
type ListEntitiesParams struct {
BankID string
// Maximum number of entities to return.
Limit OptInt `json:",omitempty,omitzero"`
// Offset for pagination.
Offset OptInt `json:",omitempty,omitzero"`
}
// ListMemoriesParams is parameters of list_memories operation.
type ListMemoriesParams struct {
BankID string
Type OptString `json:",omitempty,omitzero"`
Q OptString `json:",omitempty,omitzero"`
Limit OptInt `json:",omitempty,omitzero"`
Offset OptInt `json:",omitempty,omitzero"`
}
// ListMentalModelsParams is parameters of list_mental_models operation.
type ListMentalModelsParams struct {
BankID string
// Filter by tags.
Tags []string `json:",omitempty"`
// How to match tags.
TagsMatch OptListMentalModelsTagsMatch `json:",omitempty,omitzero"`
Limit OptInt `json:",omitempty,omitzero"`
Offset OptInt `json:",omitempty,omitzero"`
}
// ListOperationsParams is parameters of list_operations operation.
type ListOperationsParams struct {
BankID string
// Filter by status: pending, completed, or failed.
Status OptString `json:",omitempty,omitzero"`
// Maximum number of operations to return.
Limit OptInt `json:",omitempty,omitzero"`
// Number of operations to skip.
Offset OptInt `json:",omitempty,omitzero"`
}
// ListTagsParams is parameters of list_tags operation.
type ListTagsParams struct {
BankID string
// Wildcard pattern to filter tags (e.g., 'user:*' for user:alice, '*-admin' for role-admin). Use '*'
// as wildcard. Case-insensitive.
Q OptString `json:",omitempty,omitzero"`
// Maximum number of tags to return.
Limit OptInt `json:",omitempty,omitzero"`
// Offset for pagination.
Offset OptInt `json:",omitempty,omitzero"`
}
// RecallMemoriesParams is parameters of recall_memories operation.
type RecallMemoriesParams struct {
BankID string
}
// ReflectParams is parameters of reflect operation.
type ReflectParams struct {
BankID string
}
// RefreshMentalModelParams is parameters of refresh_mental_model operation.
type RefreshMentalModelParams struct {
BankID string
MentalModelID string
}
// RegenerateEntityObservationsParams is parameters of regenerate_entity_observations operation.
type RegenerateEntityObservationsParams struct {
BankID string
EntityID string
}
// ResetBankConfigParams is parameters of reset_bank_config operation.
type ResetBankConfigParams struct {
BankID string
}
// RetainMemoriesParams is parameters of retain_memories operation.
type RetainMemoriesParams struct {
BankID string
}
// TriggerConsolidationParams is parameters of trigger_consolidation operation.
type TriggerConsolidationParams struct {
BankID string
}
// UpdateBankParams is parameters of update_bank operation.
type UpdateBankParams struct {
BankID string
}
// UpdateBankConfigParams is parameters of update_bank_config operation.
type UpdateBankConfigParams struct {
BankID string
}
// UpdateBankDispositionParams is parameters of update_bank_disposition operation.
type UpdateBankDispositionParams struct {
BankID string
}
// UpdateDirectiveParams is parameters of update_directive operation.
type UpdateDirectiveParams struct {
BankID string
DirectiveID string
}
// UpdateMentalModelParams is parameters of update_mental_model operation.
type UpdateMentalModelParams struct {
BankID string
MentalModelID string
}

View file

@ -0,0 +1,179 @@
// Code generated by ogen, DO NOT EDIT.
package ogenapi
import (
"bytes"
"net/http"
"github.com/go-faster/jx"
ht "github.com/ogen-go/ogen/http"
)
func encodeAddBankBackgroundRequest(
req *AddBackgroundRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeCreateDirectiveRequest(
req *CreateDirectiveRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeCreateMentalModelRequest(
req *CreateMentalModelRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeCreateOrUpdateBankRequest(
req *CreateBankRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeRecallMemoriesRequest(
req *RecallRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeReflectRequest(
req *ReflectRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeRetainMemoriesRequest(
req *RetainRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeUpdateBankRequest(
req *CreateBankRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeUpdateBankConfigRequest(
req *BankConfigUpdate,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeUpdateBankDispositionRequest(
req *UpdateDispositionRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeUpdateDirectiveRequest(
req *UpdateDirectiveRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeUpdateMentalModelRequest(
req *UpdateMentalModelRequest,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,935 @@
// Code generated by ogen, DO NOT EDIT.
package ogenapi
import (
"fmt"
"github.com/go-faster/errors"
"github.com/ogen-go/ogen/validate"
)
func (s *BackgroundResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.Disposition.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "disposition",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *BankListItem) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Disposition.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "disposition",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *BankListResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Banks == nil {
return errors.New("nil is invalid value")
}
var failures []validate.FieldError
for i, elem := range s.Banks {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: fmt.Sprintf("[%d]", i),
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "banks",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *BankProfileResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Disposition.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "disposition",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s Budget) Validate() error {
switch s {
case "low":
return nil
case "mid":
return nil
case "high":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}
func (s *CreateBankRequest) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.Disposition.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "disposition",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *CreateMentalModelRequest) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.MaxTokens.Get(); ok {
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 256,
MaxSet: true,
Max: 8192,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
Pattern: nil,
}).Validate(int64(value)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "max_tokens",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *DirectiveListResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Items == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "items",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *DispositionTraits) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 1,
MaxSet: true,
Max: 5,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
Pattern: nil,
}).Validate(int64(s.Empathy)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "empathy",
Error: err,
})
}
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 1,
MaxSet: true,
Max: 5,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
Pattern: nil,
}).Validate(int64(s.Literalism)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "literalism",
Error: err,
})
}
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 1,
MaxSet: true,
Max: 5,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
Pattern: nil,
}).Validate(int64(s.Skepticism)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "skepticism",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *EntityDetailResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Observations == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "observations",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *EntityListResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Items == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "items",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *EntityStateResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Observations == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "observations",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *GraphDataResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Edges == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "edges",
Error: err,
})
}
if err := func() error {
if s.Nodes == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "nodes",
Error: err,
})
}
if err := func() error {
if s.TableRows == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "table_rows",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *HTTPValidationError) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
var failures []validate.FieldError
for i, elem := range s.Detail {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: fmt.Sprintf("[%d]", i),
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "detail",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s ListDirectivesTagsMatch) Validate() error {
switch s {
case "any":
return nil
case "all":
return nil
case "exact":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}
func (s *ListDocumentsResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Items == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "items",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ListMemoryUnitsResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Items == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "items",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s ListMentalModelsTagsMatch) Validate() error {
switch s {
case "any":
return nil
case "all":
return nil
case "exact":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}
func (s *ListTagsResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Items == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "items",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *MentalModelListResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Items == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "items",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *OperationStatusResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Status.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "status",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s OperationStatusResponseStatus) Validate() error {
switch s {
case "pending":
return nil
case "completed":
return nil
case "failed":
return nil
case "not_found":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}
func (s *OperationsListResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Operations == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "operations",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *RecallRequest) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.Budget.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "budget",
Error: err,
})
}
if err := func() error {
if value, ok := s.TagsMatch.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "tags_match",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s RecallRequestTagsMatch) Validate() error {
switch s {
case "any":
return nil
case "all":
return nil
case "any_strict":
return nil
case "all_strict":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}
func (s *RecallResponse) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.Entities.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "entities",
Error: err,
})
}
if err := func() error {
if s.Results == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "results",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s RecallResponseEntities) Validate() error {
var failures []validate.FieldError
for key, elem := range s {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: key,
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ReflectRequest) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.Budget.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "budget",
Error: err,
})
}
if err := func() error {
if value, ok := s.TagsMatch.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "tags_match",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s ReflectRequestTagsMatch) Validate() error {
switch s {
case "any":
return nil
case "all":
return nil
case "any_strict":
return nil
case "all_strict":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}
func (s *RetainRequest) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Items == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "items",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *UpdateDispositionRequest) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Disposition.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "disposition",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *UpdateMentalModelRequest) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.MaxTokens.Get(); ok {
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 256,
MaxSet: true,
Max: 8192,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
Pattern: nil,
}).Validate(int64(value)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "max_tokens",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ValidationError) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if s.Loc == nil {
return errors.New("nil is invalid value")
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "loc",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}

View file

@ -0,0 +1,9 @@
generator:
ignore_not_implemented: ["all"]
features:
disable:
- paths/server
- webhooks/server
- webhooks/client
- ogen/otel
- ogen/unimplemented

View file

@ -0,0 +1,321 @@
package hindsight
import (
"net/http"
"time"
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
)
// --- Client options ---
type clientConfig struct {
apiKey string
httpClient *http.Client
}
// Option configures a [Client].
type Option func(*clientConfig)
// WithAPIKey sets the Bearer token used for authentication.
func WithAPIKey(key string) Option {
return func(c *clientConfig) { c.apiKey = key }
}
// WithHTTPClient sets a custom [http.Client] for all requests.
// If combined with [WithAPIKey], the API key transport wraps this client's transport.
func WithHTTPClient(hc *http.Client) Option {
return func(c *clientConfig) { c.httpClient = hc }
}
// --- Re-exported types ---
// Budget controls the computation budget for recall and reflect operations.
type Budget = ogenapi.Budget
const (
BudgetLow = ogenapi.BudgetLow
BudgetMid = ogenapi.BudgetMid
BudgetHigh = ogenapi.BudgetHigh
)
// TagsMatch controls how tag filtering works.
type TagsMatch string
const (
TagsMatchAny TagsMatch = "any"
TagsMatchAll TagsMatch = "all"
TagsMatchAnyStrict TagsMatch = "any_strict"
TagsMatchAllStrict TagsMatch = "all_strict"
)
type (
// RetainResponse is the response from a retain operation.
RetainResponse = ogenapi.RetainResponse
// RecallResponse is the response from a recall operation.
RecallResponse = ogenapi.RecallResponse
// RecallResult is a single memory result from recall.
RecallResult = ogenapi.RecallResult
// ReflectResponse is the response from a reflect operation.
ReflectResponse = ogenapi.ReflectResponse
// MemoryItem is a single memory item for retain operations.
MemoryItem = ogenapi.MemoryItem
// EntityInput provides entity hints for retain.
EntityInput = ogenapi.EntityInput
// BankProfileResponse is the response for bank profile operations.
BankProfileResponse = ogenapi.BankProfileResponse
// BankListResponse is the response for listing banks.
BankListResponse = ogenapi.BankListResponse
// DispositionTraits configures personality traits for a bank.
DispositionTraits = ogenapi.DispositionTraits
// TokenUsage reports LLM token consumption.
TokenUsage = ogenapi.TokenUsage
// ReflectBasedOn contains the evidence used for a reflect response.
ReflectBasedOn = ogenapi.ReflectBasedOn
// IncludeOptions controls what extra data is returned from recall.
IncludeOptions = ogenapi.IncludeOptions
// ReflectIncludeOptions controls what extra data is returned from reflect.
ReflectIncludeOptions = ogenapi.ReflectIncludeOptions
)
// --- Retain options ---
// RetainOption configures a [Client.Retain] call.
type RetainOption func(*retainConfig)
type retainConfig struct {
timestamp *time.Time
context *string
documentID *string
metadata map[string]string
entities []EntityInput
tags []string
}
// WithTimestamp sets the timestamp for a retained memory.
func WithTimestamp(t time.Time) RetainOption {
return func(c *retainConfig) { c.timestamp = &t }
}
// WithContext sets additional context for a retained memory.
func WithContext(ctx string) RetainOption {
return func(c *retainConfig) { c.context = &ctx }
}
// WithDocumentID groups retained memories under a document.
func WithDocumentID(id string) RetainOption {
return func(c *retainConfig) { c.documentID = &id }
}
// WithMetadata attaches key-value metadata to a retained memory.
func WithMetadata(m map[string]string) RetainOption {
return func(c *retainConfig) { c.metadata = m }
}
// WithEntities provides entity hints for a retained memory.
func WithEntities(e []EntityInput) RetainOption {
return func(c *retainConfig) { c.entities = e }
}
// WithTags attaches tags to a retained memory for filtering.
func WithTags(tags []string) RetainOption {
return func(c *retainConfig) { c.tags = tags }
}
// RetainBatchOption configures a [Client.RetainBatch] call.
type RetainBatchOption func(*retainBatchConfig)
type retainBatchConfig struct {
documentTags []string
async bool
}
// WithDocumentTags sets tags applied to all items in a batch retain.
func WithDocumentTags(tags []string) RetainBatchOption {
return func(c *retainBatchConfig) { c.documentTags = tags }
}
// WithAsync processes the retain batch asynchronously.
func WithAsync(async bool) RetainBatchOption {
return func(c *retainBatchConfig) { c.async = async }
}
// --- Recall options ---
// RecallOption configures a [Client.Recall] call.
type RecallOption func(*recallConfig)
type recallConfig struct {
types []string
maxTokens *int
budget *Budget
trace *bool
queryTimestamp *string
includeOpts *IncludeOptions
tags []string
tagsMatch *TagsMatch
}
// WithTypes filters recalled memories by type (e.g., "world", "experience").
func WithTypes(types []string) RecallOption {
return func(c *recallConfig) { c.types = types }
}
// WithMaxTokens sets the maximum tokens for recall results.
func WithMaxTokens(n int) RecallOption {
return func(c *recallConfig) { c.maxTokens = &n }
}
// WithBudget sets the computation budget for recall.
func WithBudget(b Budget) RecallOption {
return func(c *recallConfig) { c.budget = &b }
}
// WithTrace enables the execution trace in recall results.
func WithTrace(enabled bool) RecallOption {
return func(c *recallConfig) { c.trace = &enabled }
}
// WithQueryTimestamp sets the temporal context for recall (ISO 8601 format).
func WithQueryTimestamp(ts string) RecallOption {
return func(c *recallConfig) { c.queryTimestamp = &ts }
}
// WithInclude configures which additional data to include in recall results.
func WithInclude(opts IncludeOptions) RecallOption {
return func(c *recallConfig) { c.includeOpts = &opts }
}
// WithRecallTags filters recalled memories by tags.
func WithRecallTags(tags []string) RecallOption {
return func(c *recallConfig) { c.tags = tags }
}
// WithRecallTagsMatch sets how tags are matched during recall.
func WithRecallTagsMatch(m TagsMatch) RecallOption {
return func(c *recallConfig) { c.tagsMatch = &m }
}
// --- Reflect options ---
// ReflectOption configures a [Client.Reflect] call.
type ReflectOption func(*reflectConfig)
type reflectConfig struct {
budget *Budget
maxTokens *int
includeOpts *ReflectIncludeOptions
responseSchema map[string]any
tags []string
tagsMatch *TagsMatch
}
// WithReflectBudget sets the computation budget for reflect.
func WithReflectBudget(b Budget) ReflectOption {
return func(c *reflectConfig) { c.budget = &b }
}
// WithReflectMaxTokens sets the maximum tokens for the reflect response.
func WithReflectMaxTokens(n int) ReflectOption {
return func(c *reflectConfig) { c.maxTokens = &n }
}
// WithReflectInclude configures which additional data to include in reflect results.
func WithReflectInclude(opts ReflectIncludeOptions) ReflectOption {
return func(c *reflectConfig) { c.includeOpts = &opts }
}
// WithResponseSchema sets a JSON Schema for structured output from reflect.
func WithResponseSchema(schema map[string]any) ReflectOption {
return func(c *reflectConfig) { c.responseSchema = schema }
}
// WithReflectTags filters memories by tags during reflect.
func WithReflectTags(tags []string) ReflectOption {
return func(c *reflectConfig) { c.tags = tags }
}
// WithReflectTagsMatch sets how tags are matched during reflect.
func WithReflectTagsMatch(m TagsMatch) ReflectOption {
return func(c *reflectConfig) { c.tagsMatch = &m }
}
// --- Bank options ---
// CreateBankOption configures a [Client.CreateBank] call.
type CreateBankOption func(*createBankConfig)
type createBankConfig struct {
name *string
mission *string
disposition *DispositionTraits
}
// WithBankName sets the display name for a bank.
func WithBankName(name string) CreateBankOption {
return func(c *createBankConfig) { c.name = &name }
}
// WithMission sets the mission for a bank.
func WithMission(mission string) CreateBankOption {
return func(c *createBankConfig) { c.mission = &mission }
}
// WithDisposition sets the personality traits for a bank.
func WithDisposition(d DispositionTraits) CreateBankOption {
return func(c *createBankConfig) { c.disposition = &d }
}
// --- helpers ---
func optString(s string) ogenapi.OptString {
return ogenapi.NewOptString(s)
}
func optStringPtr(s *string) ogenapi.OptString {
if s == nil {
return ogenapi.OptString{}
}
return ogenapi.NewOptString(*s)
}
func optInt(n int) ogenapi.OptInt {
return ogenapi.NewOptInt(n)
}
func optIntPtr(n *int) ogenapi.OptInt {
if n == nil {
return ogenapi.OptInt{}
}
return ogenapi.NewOptInt(*n)
}
func optBool(b bool) ogenapi.OptBool {
return ogenapi.NewOptBool(b)
}
func optBoolPtr(b *bool) ogenapi.OptBool {
if b == nil {
return ogenapi.OptBool{}
}
return ogenapi.NewOptBool(*b)
}
func optBudget(b *Budget) ogenapi.OptBudget {
if b == nil {
return ogenapi.OptBudget{}
}
return ogenapi.NewOptBudget(*b)
}

View file

@ -0,0 +1,59 @@
package hindsight
import (
"context"
"fmt"
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
)
// Recall retrieves memories from the given bank that match the query.
func (c *Client) Recall(ctx context.Context, bankID, query string, opts ...RecallOption) (*RecallResponse, error) {
var cfg recallConfig
for _, o := range opts {
o(&cfg)
}
req := &ogenapi.RecallRequest{
Query: query,
}
if cfg.budget != nil {
req.Budget = ogenapi.NewOptBudget(*cfg.budget)
}
if cfg.maxTokens != nil {
req.MaxTokens = ogenapi.NewOptInt(*cfg.maxTokens)
}
if cfg.trace != nil {
req.Trace = ogenapi.NewOptBool(*cfg.trace)
}
if cfg.queryTimestamp != nil {
req.QueryTimestamp = ogenapi.NewOptString(*cfg.queryTimestamp)
}
if cfg.types != nil {
req.Types = cfg.types
}
if cfg.includeOpts != nil {
req.Include = ogenapi.NewOptIncludeOptions(*cfg.includeOpts)
}
if cfg.tags != nil {
req.Tags = cfg.tags
}
if cfg.tagsMatch != nil {
req.TagsMatch = ogenapi.NewOptRecallRequestTagsMatch(
ogenapi.RecallRequestTagsMatch(*cfg.tagsMatch),
)
}
res, err := c.api.RecallMemories(ctx, req, ogenapi.RecallMemoriesParams{
BankID: bankID,
})
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.RecallResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}

View file

@ -0,0 +1,74 @@
package hindsight
import (
"context"
"encoding/json"
"fmt"
"github.com/go-faster/jx"
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
)
// Reflect performs disposition-aware reasoning using the bank's memories and
// mental models. Returns a markdown-formatted response.
func (c *Client) Reflect(ctx context.Context, bankID, query string, opts ...ReflectOption) (*ReflectResponse, error) {
var cfg reflectConfig
for _, o := range opts {
o(&cfg)
}
req := &ogenapi.ReflectRequest{
Query: query,
}
if cfg.budget != nil {
req.Budget = ogenapi.NewOptBudget(*cfg.budget)
}
if cfg.maxTokens != nil {
req.MaxTokens = ogenapi.NewOptInt(*cfg.maxTokens)
}
if cfg.includeOpts != nil {
req.Include = ogenapi.NewOptReflectIncludeOptions(*cfg.includeOpts)
}
if cfg.responseSchema != nil {
schema, err := toResponseSchema(cfg.responseSchema)
if err != nil {
return nil, fmt.Errorf("hindsight: marshal response_schema: %w", err)
}
req.ResponseSchema = ogenapi.NewOptReflectRequestResponseSchema(schema)
}
if cfg.tags != nil {
req.Tags = cfg.tags
}
if cfg.tagsMatch != nil {
req.TagsMatch = ogenapi.NewOptReflectRequestTagsMatch(
ogenapi.ReflectRequestTagsMatch(*cfg.tagsMatch),
)
}
res, err := c.api.Reflect(ctx, req, ogenapi.ReflectParams{
BankID: bankID,
})
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.ReflectResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}
// toResponseSchema converts a map[string]any JSON schema to the ogen type.
func toResponseSchema(schema map[string]any) (ogenapi.ReflectRequestResponseSchema, error) {
out := make(ogenapi.ReflectRequestResponseSchema, len(schema))
for k, v := range schema {
data, err := json.Marshal(v)
if err != nil {
return nil, err
}
out[k] = jx.Raw(data)
}
return out, nil
}

View file

@ -0,0 +1,73 @@
package hindsight
import (
"context"
"fmt"
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
)
// Retain stores a single memory in the given bank.
// It wraps [Client.RetainBatch] for convenience.
func (c *Client) Retain(ctx context.Context, bankID, content string, opts ...RetainOption) (*RetainResponse, error) {
var cfg retainConfig
for _, o := range opts {
o(&cfg)
}
item := ogenapi.MemoryItem{
Content: content,
}
if cfg.timestamp != nil {
item.Timestamp = ogenapi.NewOptDateTime(*cfg.timestamp)
}
if cfg.context != nil {
item.Context = ogenapi.NewOptString(*cfg.context)
}
if cfg.documentID != nil {
item.DocumentID = ogenapi.NewOptString(*cfg.documentID)
}
if cfg.metadata != nil {
m := ogenapi.MemoryItemMetadata(cfg.metadata)
item.Metadata = ogenapi.NewOptMemoryItemMetadata(m)
}
if cfg.entities != nil {
item.Entities = cfg.entities
}
if cfg.tags != nil {
item.Tags = cfg.tags
}
return c.RetainBatch(ctx, bankID, []MemoryItem{item})
}
// RetainBatch stores multiple memories in the given bank.
func (c *Client) RetainBatch(ctx context.Context, bankID string, items []MemoryItem, opts ...RetainBatchOption) (*RetainResponse, error) {
var cfg retainBatchConfig
for _, o := range opts {
o(&cfg)
}
req := &ogenapi.RetainRequest{
Items: items,
}
if cfg.async {
req.Async = ogenapi.NewOptBool(true)
}
if cfg.documentTags != nil {
req.DocumentTags = cfg.documentTags
}
res, err := c.api.RetainMemories(ctx, req, ogenapi.RetainMemoriesParams{
BankID: bankID,
})
if err != nil {
return nil, err
}
resp, ok := res.(*ogenapi.RetainResponse)
if !ok {
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
}
return resp, nil
}

View file

@ -0,0 +1,345 @@
---
sidebar_position: 10
---
# Go Memory-Augmented API
:::info Complete Application
This is a complete, runnable Go application demonstrating Hindsight integration.
:::
A Go HTTP microservice that combines a domain API with Hindsight memory. Each API user gets a personal memory bank. The service remembers past interactions and uses them to provide personalized responses.
## Use Case
A **developer knowledge assistant** that remembers what technologies each user works with, what problems they've solved, and provides personalized recommendations.
## Features
- Per-user memory banks (created on first interaction)
- Conversation history stored via retain
- Context-aware responses via recall + reflect
- Structured output for programmatic consumption
- Health check and bank stats endpoints
## Project Structure
```
go-memory-service/
├── main.go # HTTP server, routes, handlers
├── go.mod
└── go.sum
```
## The Code
```go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
var client *hindsight.Client
func main() {
apiURL := envOr("HINDSIGHT_API_URL", "http://localhost:8888")
var err error
client, err = hindsight.New(apiURL)
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.HandleFunc("POST /ask", handleAsk)
mux.HandleFunc("POST /learn", handleLearn)
mux.HandleFunc("GET /recall/{userID}", handleRecall)
mux.HandleFunc("GET /health", handleHealth)
addr := envOr("ADDR", ":8080")
log.Printf("listening on %s (hindsight: %s)", addr, apiURL)
log.Fatal(http.ListenAndServe(addr, mux))
}
// --- Request/Response types ---
type AskRequest struct {
UserID string `json:"user_id"`
Query string `json:"query"`
}
type AskResponse struct {
Answer string `json:"answer"`
Facts []string `json:"facts,omitempty"`
}
type LearnRequest struct {
UserID string `json:"user_id"`
Content string `json:"content"`
Tags []string `json:"tags,omitempty"`
}
type RecallResponse struct {
Results []RecallFact `json:"results"`
}
type RecallFact struct {
Text string `json:"text"`
Type string `json:"type"`
}
// --- Handlers ---
// handleLearn stores new information for a user.
func handleLearn(w http.ResponseWriter, r *http.Request) {
var req LearnRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
ctx := r.Context()
bankID := bankFor(req.UserID)
// Ensure bank exists
ensureBank(ctx, bankID, req.UserID)
// Store the memory
var opts []hindsight.RetainOption
if len(req.Tags) > 0 {
opts = append(opts, hindsight.WithTags(req.Tags))
}
resp, err := client.Retain(ctx, bankID, req.Content, opts...)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]any{
"success": resp.Success,
"bank_id": bankID,
})
}
// handleAsk answers a question using the user's memories.
func handleAsk(w http.ResponseWriter, r *http.Request) {
var req AskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
ctx := r.Context()
bankID := bankFor(req.UserID)
// Ensure bank exists
ensureBank(ctx, bankID, req.UserID)
// Recall relevant facts
recallResp, err := client.Recall(ctx, bankID, req.Query,
hindsight.WithBudget(hindsight.BudgetMid),
hindsight.WithMaxTokens(2048),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var facts []string
for _, result := range recallResp.Results {
facts = append(facts, result.Text)
}
// Reflect to generate an answer
reflectResp, err := client.Reflect(ctx, bankID, req.Query,
hindsight.WithReflectBudget(hindsight.BudgetMid),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Store this interaction as a new memory
interaction := fmt.Sprintf("User asked: %q\nAssistant answered: %s", req.Query, reflectResp.Text)
go func() {
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client.Retain(bgCtx, bankID, interaction,
hindsight.WithContext("Q&A interaction"),
)
}()
writeJSON(w, AskResponse{
Answer: reflectResp.Text,
Facts: facts,
})
}
// handleRecall returns raw memories for a user.
func handleRecall(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("userID")
query := r.URL.Query().Get("q")
if query == "" {
query = "What do you know?"
}
ctx := r.Context()
bankID := bankFor(userID)
resp, err := client.Recall(ctx, bankID, query,
hindsight.WithBudget(hindsight.BudgetHigh),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var results []RecallFact
for _, result := range resp.Results {
results = append(results, RecallFact{
Text: result.Text,
Type: result.Type.Or("unknown"),
})
}
writeJSON(w, RecallResponse{Results: results})
}
func handleHealth(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, map[string]string{"status": "ok"})
}
// --- Helpers ---
func bankFor(userID string) string {
return "user-" + strings.ToLower(userID)
}
func ensureBank(ctx context.Context, bankID, userID string) {
client.CreateBank(ctx, bankID,
hindsight.WithBankName(fmt.Sprintf("Memory for %s", userID)),
hindsight.WithMission("Developer knowledge assistant. Remember technologies, problems solved, and preferences."),
)
}
func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(v)
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
```
## Running
### 1. Start Hindsight
```bash
export OPENAI_API_KEY=your-key
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
ghcr.io/vectorize-io/hindsight:latest
```
### 2. Start the service
```bash
go run main.go
```
### 3. Try it out
```bash
# Teach it something
curl -s localhost:8080/learn -d '{
"user_id": "alice",
"content": "I am building a Go microservice that uses gRPC and PostgreSQL",
"tags": ["project"]
}' | jq .
# Teach it more
curl -s localhost:8080/learn -d '{
"user_id": "alice",
"content": "I solved a connection pooling issue by switching from pgx to pgxpool",
"tags": ["debugging"]
}'
curl -s localhost:8080/learn -d '{
"user_id": "alice",
"content": "I prefer structured logging with slog over zerolog",
"tags": ["preferences"]
}'
# Ask a question (uses recall + reflect)
curl -s localhost:8080/ask -d '{
"user_id": "alice",
"query": "What tech stack am I using?"
}' | jq .
# Raw recall
curl -s "localhost:8080/recall/alice?q=database" | jq .
```
## How It Works
1. **`/learn`** - Stores information using `Retain`. Each piece of info becomes searchable facts, entities, and relationships.
2. **`/ask`** - Two-phase retrieval:
- **Recall**: Finds relevant facts from the user's memory bank
- **Reflect**: Synthesizes a response using those facts plus disposition-aware reasoning
- The Q&A interaction itself is stored as a new memory (fire-and-forget goroutine)
3. **`/recall/{userID}`** - Direct access to raw recalled facts for debugging or building custom UIs.
## Key Patterns
### Per-User Isolation
Each user gets their own bank (`user-alice`, `user-bob`). Banks are created lazily on first interaction via `CreateBank` (idempotent - safe to call repeatedly).
### Fire-and-Forget Memory
The `/ask` handler stores the interaction in a background goroutine so the response isn't delayed by the retain call:
```go
go func() {
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client.Retain(bgCtx, bankID, interaction)
}()
```
### Tag-Based Scoping
Tags partition memories within a bank. Query only `debugging` memories, or only `preferences`:
```bash
# The /recall endpoint could be extended to support tag filtering:
curl "localhost:8080/recall/alice?q=issues&tags=debugging"
```
## Next Steps
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Core operations walkthrough
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Bulk data ingestion
- [Per-User Memory](/cookbook/recipes/per-user-memory) - The pattern in depth
- [Go SDK Reference](/sdks/go) - Full API documentation

View file

@ -86,6 +86,18 @@ Learn how to build with Hindsight through practical examples:
href: "/cookbook/recipes/study_buddy",
description: "Track study sessions, identify knowledge gaps, and get personalized review suggestions",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Go Quickstart",
href: "/cookbook/recipes/go-quickstart",
description: "Get started with the Go client: retain, recall, and reflect",
tags: { sdk: "hindsight-go", topic: "Quick Start" }
},
{
title: "Go Concurrent Pipeline",
href: "/cookbook/recipes/go-concurrent-pipeline",
description: "Build a concurrent memory ingestion pipeline with goroutines",
tags: { sdk: "hindsight-go", topic: "Learning" }
}
]}
/>
@ -140,6 +152,12 @@ Learn how to build with Hindsight through practical examples:
href: "/cookbook/applications/taste-ai",
description: "Personal food assistant with AI SDK v6 showcasing recall, mental models, and directives",
tags: { sdk: "@vectorize-io/hindsight-ai-sdk", topic: "Recommendation" }
},
{
title: "Go Memory-Augmented API",
href: "/cookbook/applications/go-memory-service",
description: "Go HTTP microservice with per-user memory banks for a developer knowledge assistant",
tags: { sdk: "hindsight-go", topic: "Learning" }
}
]}
/>

View file

@ -0,0 +1,272 @@
---
sidebar_position: 13
---
# Go Concurrent Pipeline
Build a concurrent memory ingestion pipeline in Go. This recipe demonstrates how to use Go's concurrency primitives with the Hindsight client to ingest large datasets efficiently, then query them with recall and reflect.
## The Problem
You have a large dataset (log files, chat transcripts, documents) that needs to be ingested into Hindsight. Sequential ingestion is slow. Go's goroutines make it straightforward to parallelize.
## Architecture
```
┌──────────┐
│ Source │
│ (files, │
│ API, DB) │
└────┬─────┘
┌────▼─────┐
│ Producer │ reads data, sends to channel
└────┬─────┘
┌──────────┼──────────┐
│ │ │
┌────▼───┐ ┌───▼────┐ ┌──▼─────┐
│Worker 1│ │Worker 2│ │Worker N│ concurrent RetainBatch
└────┬───┘ └───┬────┘ └──┬─────┘
│ │ │
└─────────┼─────────┘
┌─────▼─────┐
│ Hindsight │
│ API │
└───────────┘
```
## Prerequisites
Hindsight server running (see [Go Quickstart](/cookbook/recipes/go-quickstart) for setup).
## The Pipeline
```go
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"sync"
"sync/atomic"
"time"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
const (
batchSize = 10 // memories per batch
numWorkers = 4 // concurrent workers
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
bankID := "pipeline-demo"
// Create the bank
client.CreateBank(ctx, bankID,
hindsight.WithBankName("Pipeline Demo"),
hindsight.WithMission("Knowledge base ingested from documents"),
)
// Ingest from a file (one line = one memory)
if len(os.Args) < 2 {
fmt.Println("usage: pipeline <file>")
os.Exit(1)
}
start := time.Now()
count := ingest(ctx, client, bankID, os.Args[1])
elapsed := time.Since(start)
fmt.Printf("\nIngested %d memories in %s (%.1f/sec)\n",
count, elapsed, float64(count)/elapsed.Seconds())
// Query the ingested data
fmt.Println("\n--- Recall ---")
resp, _ := client.Recall(ctx, bankID, "What are the key topics?",
hindsight.WithBudget(hindsight.BudgetHigh),
)
for _, r := range resp.Results {
fmt.Printf(" - %s\n", r.Text)
}
fmt.Println("\n--- Reflect ---")
answer, _ := client.Reflect(ctx, bankID, "Summarize everything you know")
fmt.Println(answer.Text)
}
func ingest(ctx context.Context, client *hindsight.Client, bankID, filename string) int64 {
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// Channel for batches
batches := make(chan []hindsight.MemoryItem, numWorkers*2)
var ingested atomic.Int64
// Start workers
var wg sync.WaitGroup
for i := range numWorkers {
wg.Add(1)
go func(id int) {
defer wg.Done()
for batch := range batches {
_, err := client.RetainBatch(ctx, bankID, batch)
if err != nil {
log.Printf("worker %d: batch failed: %v", id, err)
continue
}
n := ingested.Add(int64(len(batch)))
if n%100 == 0 {
fmt.Printf(" ingested %d memories...\n", n)
}
}
}(i)
}
// Producer: read lines and batch them
scanner := bufio.NewScanner(f)
var batch []hindsight.MemoryItem
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
batch = append(batch, hindsight.MemoryItem{Content: line})
if len(batch) >= batchSize {
batches <- batch
batch = nil
}
}
// Flush remaining
if len(batch) > 0 {
batches <- batch
}
close(batches)
wg.Wait()
return ingested.Load()
}
```
## Running It
Create a sample data file:
```bash
cat > sample_data.txt << 'EOF'
The Go programming language was created at Google in 2007
Go's concurrency model is based on CSP (Communicating Sequential Processes)
Goroutines are lightweight threads managed by the Go runtime
Channels provide typed conduits for communication between goroutines
The sync package provides mutexes, wait groups, and other synchronization primitives
Go modules were introduced in Go 1.11 for dependency management
The context package provides cancellation and deadline propagation
Go compiles to a single static binary with no external dependencies
The standard library includes an HTTP server, JSON parser, and crypto packages
Go 1.18 introduced generics with type parameters
EOF
```
```bash
go run . sample_data.txt
```
## Adding Tags for Partitioning
For larger datasets, use tags to partition memories by source or topic:
```go
func ingestWithTags(ctx context.Context, client *hindsight.Client, bankID, source string, lines []string) {
var items []hindsight.MemoryItem
for _, line := range lines {
items = append(items, hindsight.MemoryItem{
Content: line,
Tags: []string{source},
})
}
_, err := client.RetainBatch(ctx, bankID, items,
hindsight.WithDocumentTags([]string{"bulk-import", source}),
)
if err != nil {
log.Printf("ingest %s failed: %v", source, err)
}
}
// Later, recall only from a specific source
resp, _ := client.Recall(ctx, bankID, "What are the key concepts?",
hindsight.WithRecallTags([]string{"go-docs"}),
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
)
```
## Async Ingestion
For very large datasets, use async mode to avoid waiting for fact extraction:
```go
// Async retain returns immediately; processing happens in background
_, err := client.RetainBatch(ctx, bankID, items,
hindsight.WithAsync(true),
)
// Check operation status via the ogen client
ogen := client.OgenClient()
ops, _ := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
BankID: bankID,
})
```
## Graceful Shutdown
Use `context.WithCancel` for clean cancellation:
```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// In a signal handler:
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
go func() {
<-sigCh
fmt.Println("\nShutting down gracefully...")
cancel()
}()
// Workers will stop when ctx is cancelled
```
## Performance Tips
| Tip | Why |
|-----|-----|
| Use `RetainBatch` over individual `Retain` | Fewer HTTP round trips |
| Set `batchSize` to 10-50 | Balances throughput vs. memory |
| Use 4-8 workers | Matches typical API concurrency limits |
| Use `WithAsync(true)` for bulk imports | Returns immediately, processes in background |
| Tag your data | Enables scoped recall without reingesting |
## Next Steps
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Basic retain, recall, reflect
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
- [Go SDK Reference](/sdks/go) - Full API reference

View file

@ -0,0 +1,216 @@
---
sidebar_position: 12
---
# Go Quickstart
Get started with the Hindsight Go client in under 5 minutes. This recipe covers the three core operations: **retain**, **recall**, and **reflect**.
## Prerequisites
Make sure you have Hindsight running. The easiest way is via Docker:
```bash
export OPENAI_API_KEY=your-key
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
ghcr.io/vectorize-io/hindsight:latest
```
- API: http://localhost:8888
- UI: http://localhost:9999
## Installation
```bash
go get github.com/vectorize-io/hindsight-client-go
```
## Connect to Hindsight
```go
package main
import (
"context"
"fmt"
"log"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
bankID := "go-quickstart"
```
## Retain: Store Information
The `Retain` operation pushes new memories into Hindsight. Behind the scenes, an LLM extracts key facts, temporal data, entities, and relationships.
```go
// Simple retain
_, err = client.Retain(ctx, bankID,
"Alice works at Google as a software engineer",
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Stored memory about Alice's job")
// Retain with context and timestamp
_, err = client.Retain(ctx, bankID,
"Alice got promoted to senior engineer",
hindsight.WithContext("career update"),
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Stored memory about Alice's promotion")
```
## Retain Batch: Store Multiple Memories
```go
items := []hindsight.MemoryItem{
{Content: "Bob is a data scientist who works with Alice"},
{Content: "Charlie manages the team and reports to the VP of Engineering"},
{Content: "The team is working on a recommendation engine using Go"},
}
resp, err := client.RetainBatch(ctx, bankID, items)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Stored %d memories\n", resp.ItemsCount)
```
## Recall: Retrieve Memories
`Recall` retrieves memories matching a query using four parallel strategies: semantic similarity, keyword matching, entity/relationship graph traversal, and temporal filtering.
```go
// Simple recall
results, err := client.Recall(ctx, bankID, "What does Alice do?")
if err != nil {
log.Fatal(err)
}
fmt.Println("\nMemories about Alice:")
for _, r := range results.Results {
fmt.Printf(" - %s\n", r.Text)
}
```
```go
// Recall with options
results, err = client.Recall(ctx, bankID, "Who works on the team?",
hindsight.WithBudget(hindsight.BudgetHigh),
hindsight.WithMaxTokens(2048),
hindsight.WithTypes([]string{"world"}),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("\nTeam memories (world facts only):")
for _, r := range results.Results {
fmt.Printf(" - [%s] %s\n", r.Type.Or("?"), r.Text)
}
```
## Reflect: Generate Insights
`Reflect` performs disposition-aware reasoning over stored memories. It retrieves relevant context, then uses an LLM to synthesize a response. Great for summarization, analysis, and Q&A.
```go
answer, err := client.Reflect(ctx, bankID,
"What should I know about this team?",
)
if err != nil {
log.Fatal(err)
}
fmt.Println("\nReflection:")
fmt.Println(answer.Text)
```
## Full Program
Here's the complete program:
```go
package main
import (
"context"
"fmt"
"log"
"time"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
bankID := "go-quickstart"
// 1. Store memories
client.Retain(ctx, bankID, "Alice works at Google as a software engineer")
client.Retain(ctx, bankID, "Alice got promoted to senior engineer",
hindsight.WithContext("career update"),
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
)
items := []hindsight.MemoryItem{
{Content: "Bob is a data scientist who works with Alice"},
{Content: "Charlie manages the team and reports to the VP of Engineering"},
{Content: "The team is working on a recommendation engine using Go"},
}
client.RetainBatch(ctx, bankID, items)
// 2. Recall memories
results, _ := client.Recall(ctx, bankID, "What does Alice do?")
fmt.Println("Memories about Alice:")
for _, r := range results.Results {
fmt.Printf(" - %s\n", r.Text)
}
// 3. Reflect
answer, _ := client.Reflect(ctx, bankID, "What should I know about this team?")
fmt.Printf("\nReflection:\n%s\n", answer.Text)
// 4. Cleanup
client.DeleteBank(ctx, bankID)
}
```
## Memory Types
Hindsight organizes memory into distinct networks:
| Type | Description | Example |
|------|-------------|---------|
| **World** | Facts about the world | "Alice works at Google" |
| **Experience** | Agent's own experiences | "I helped Alice debug her code" |
| **Observation** | Complex models from reflection | "Alice is a senior IC focused on ML" |
## Next Steps
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Build a concurrent ingestion pipeline
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
- [Go SDK Reference](/sdks/go) - Full API reference

View file

@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
---
# CLI Reference

View file

@ -1,5 +1,5 @@
---
sidebar_position: 4
sidebar_position: 5
---
# Embedded SDK (hindsight-embed)

View file

@ -0,0 +1,294 @@
---
sidebar_position: 3
---
# Go Client
Official Go client for the Hindsight API, built on [ogen](https://github.com/ogen-go/ogen) for strongly-typed code generation from the OpenAPI 3.1 spec.
## Installation
```bash
go get github.com/vectorize-io/hindsight-client-go
```
Requires Go 1.25+.
## Quick Start
```go
package main
import (
"context"
"fmt"
"log"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Retain a memory
client.Retain(ctx, "my-bank", "Alice works at Google")
// Recall memories
resp, _ := client.Recall(ctx, "my-bank", "What does Alice do?")
for _, r := range resp.Results {
fmt.Println(r.Text)
}
// Reflect - generate response with disposition
answer, _ := client.Reflect(ctx, "my-bank", "Tell me about Alice")
fmt.Println(answer.Text)
}
```
## Client Initialization
```go
import hindsight "github.com/vectorize-io/hindsight-client-go"
// Default client
client, err := hindsight.New("http://localhost:8888")
// With API key authentication
client, err := hindsight.New("http://localhost:8888",
hindsight.WithAPIKey("your-api-key"),
)
// With custom HTTP client
client, err := hindsight.New("http://localhost:8888",
hindsight.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
)
```
## Core Operations
### Retain (Store Memory)
```go
// Simple
_, err := client.Retain(ctx, "my-bank", "Alice works at Google as a software engineer")
// With options
_, err := client.Retain(ctx, "my-bank", "Alice got promoted",
hindsight.WithContext("career update"),
hindsight.WithTimestamp(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)),
hindsight.WithDocumentID("conversation_001"),
hindsight.WithMetadata(map[string]string{"source": "slack"}),
hindsight.WithTags([]string{"career", "updates"}),
)
```
### Retain Batch
```go
items := []hindsight.MemoryItem{
{Content: "Alice works at Google"},
{Content: "Bob is a data scientist"},
}
_, err := client.RetainBatch(ctx, "my-bank", items,
hindsight.WithDocumentTags([]string{"team-info"}),
hindsight.WithAsync(false), // Set true for background processing
)
```
### Recall (Search)
```go
// Simple
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?")
for _, r := range resp.Results {
fmt.Printf(" %s (type: %s)\n", r.Text, r.Type.Or("unknown"))
}
// With options
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?",
hindsight.WithTypes([]string{"world", "experience"}), // Filter by fact type
hindsight.WithMaxTokens(4096),
hindsight.WithBudget(hindsight.BudgetHigh), // BudgetLow, BudgetMid, BudgetHigh
hindsight.WithTrace(true), // Include execution trace
hindsight.WithRecallTags([]string{"career"}),
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
)
```
### Reflect (Generate Response)
```go
resp, err := client.Reflect(ctx, "my-bank", "What should I know about Alice?",
hindsight.WithReflectBudget(hindsight.BudgetMid),
hindsight.WithReflectMaxTokens(2048),
)
fmt.Println(resp.Text) // Generated markdown response
```
### Reflect with Structured Output
```go
resp, err := client.Reflect(ctx, "my-bank",
"What programming language should I learn for data science?",
hindsight.WithResponseSchema(map[string]any{
"type": "object",
"properties": map[string]any{
"recommendation": map[string]any{"type": "string"},
"reasons": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
"confidence": map[string]any{"type": "string"},
},
"required": []any{"recommendation", "reasons"},
}),
hindsight.WithReflectMaxTokens(4096),
)
// resp.StructuredOutput contains the parsed JSON schema result
```
## Bank Management
### Create Bank
```go
_, err := client.CreateBank(ctx, "my-bank",
hindsight.WithBankName("Assistant"),
hindsight.WithMission("Helpful AI assistant tracking user preferences."),
hindsight.WithDisposition(hindsight.DispositionTraits{
Skepticism: 3, // 1-5: trusting to skeptical
Literalism: 3, // 1-5: flexible to literal
Empathy: 3, // 1-5: detached to empathetic
}),
)
```
### Other Bank Operations
```go
// Get bank profile
profile, err := client.GetBankProfile(ctx, "my-bank")
fmt.Println(profile.Mission)
// List all banks
banks, err := client.ListBanks(ctx)
for _, b := range banks.Banks {
fmt.Println(b.BankID)
}
// Update mission
_, err = client.SetMission(ctx, "my-bank", "New mission statement")
// Update disposition
_, err = client.UpdateDisposition(ctx, "my-bank", hindsight.DispositionTraits{
Skepticism: 4,
Literalism: 2,
Empathy: 5,
})
// Delete bank (destructive, cannot be undone)
err = client.DeleteBank(ctx, "my-bank")
```
## Tag Filtering
Tags provide visibility scoping for memories. Use them to partition memories within a bank.
```go
// Store tagged memories
client.Retain(ctx, "my-bank", "Project X meeting notes",
hindsight.WithTags([]string{"project_x", "meetings"}),
)
// Recall only project_x memories (strict - excludes untagged)
resp, _ := client.Recall(ctx, "my-bank", "What happened in meetings?",
hindsight.WithRecallTags([]string{"project_x"}),
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
)
// Reflect scoped to tags
resp, _ := client.Reflect(ctx, "my-bank", "Summarize project X",
hindsight.WithReflectTags([]string{"project_x"}),
hindsight.WithReflectTagsMatch(hindsight.TagsMatchAnyStrict),
)
```
| Match Mode | Behavior |
|-----------|----------|
| `TagsMatchAny` | OR matching, includes untagged memories |
| `TagsMatchAll` | AND matching, includes untagged memories |
| `TagsMatchAnyStrict` | OR matching, excludes untagged memories |
| `TagsMatchAllStrict` | AND matching, excludes untagged memories |
## Advanced Usage (ogen Client)
For operations not covered by the high-level wrapper (documents, entities, mental models, directives, operations), access the generated ogen client directly:
```go
import "github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
ogen := client.OgenClient()
// List entities
entities, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{
BankID: "my-bank",
})
// Create a mental model
model, err := ogen.CreateMentalModel(ctx,
&ogenapi.CreateMentalModelRequest{
Name: ogenapi.NewOptString("User Preferences"),
SourceQuery: "What are this user's preferences and habits?",
},
ogenapi.CreateMentalModelParams{BankID: "my-bank"},
)
// Create a directive
directive, err := ogen.CreateDirective(ctx,
&ogenapi.CreateDirectiveRequest{
Name: "Response Style",
Content: "Always respond in a friendly, concise manner",
},
ogenapi.CreateDirectiveParams{BankID: "my-bank"},
)
// List operations (async tasks)
ops, err := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
BankID: "my-bank",
})
// Get bank stats
stats, err := ogen.GetAgentStats(ctx, ogenapi.GetAgentStatsParams{
BankID: "my-bank",
})
```
## Error Handling
The client returns standard Go errors. HTTP errors from the API are returned as ogen error types:
```go
resp, err := client.Recall(ctx, "nonexistent-bank", "query")
if err != nil {
// Handle error - could be network, HTTP 4xx/5xx, etc.
log.Printf("recall failed: %v", err)
}
```
## Code Generation
The Go client is built on [ogen](https://github.com/ogen-go/ogen). The generated code lives in `internal/ogenapi/` and provides full type safety with no `interface{}` or reflection.
To regenerate after API changes:
```bash
cd hindsight-clients/go
go generate ./...
go build ./...
```

View file

@ -0,0 +1,345 @@
---
sidebar_position: 10
---
# Go Memory-Augmented API
:::info Complete Application
This is a complete, runnable Go application demonstrating Hindsight integration.
:::
A Go HTTP microservice that combines a domain API with Hindsight memory. Each API user gets a personal memory bank. The service remembers past interactions and uses them to provide personalized responses.
## Use Case
A **developer knowledge assistant** that remembers what technologies each user works with, what problems they've solved, and provides personalized recommendations.
## Features
- Per-user memory banks (created on first interaction)
- Conversation history stored via retain
- Context-aware responses via recall + reflect
- Structured output for programmatic consumption
- Health check and bank stats endpoints
## Project Structure
```
go-memory-service/
├── main.go # HTTP server, routes, handlers
├── go.mod
└── go.sum
```
## The Code
```go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
var client *hindsight.Client
func main() {
apiURL := envOr("HINDSIGHT_API_URL", "http://localhost:8888")
var err error
client, err = hindsight.New(apiURL)
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.HandleFunc("POST /ask", handleAsk)
mux.HandleFunc("POST /learn", handleLearn)
mux.HandleFunc("GET /recall/{userID}", handleRecall)
mux.HandleFunc("GET /health", handleHealth)
addr := envOr("ADDR", ":8080")
log.Printf("listening on %s (hindsight: %s)", addr, apiURL)
log.Fatal(http.ListenAndServe(addr, mux))
}
// --- Request/Response types ---
type AskRequest struct {
UserID string `json:"user_id"`
Query string `json:"query"`
}
type AskResponse struct {
Answer string `json:"answer"`
Facts []string `json:"facts,omitempty"`
}
type LearnRequest struct {
UserID string `json:"user_id"`
Content string `json:"content"`
Tags []string `json:"tags,omitempty"`
}
type RecallResponse struct {
Results []RecallFact `json:"results"`
}
type RecallFact struct {
Text string `json:"text"`
Type string `json:"type"`
}
// --- Handlers ---
// handleLearn stores new information for a user.
func handleLearn(w http.ResponseWriter, r *http.Request) {
var req LearnRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
ctx := r.Context()
bankID := bankFor(req.UserID)
// Ensure bank exists
ensureBank(ctx, bankID, req.UserID)
// Store the memory
var opts []hindsight.RetainOption
if len(req.Tags) > 0 {
opts = append(opts, hindsight.WithTags(req.Tags))
}
resp, err := client.Retain(ctx, bankID, req.Content, opts...)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]any{
"success": resp.Success,
"bank_id": bankID,
})
}
// handleAsk answers a question using the user's memories.
func handleAsk(w http.ResponseWriter, r *http.Request) {
var req AskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
ctx := r.Context()
bankID := bankFor(req.UserID)
// Ensure bank exists
ensureBank(ctx, bankID, req.UserID)
// Recall relevant facts
recallResp, err := client.Recall(ctx, bankID, req.Query,
hindsight.WithBudget(hindsight.BudgetMid),
hindsight.WithMaxTokens(2048),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var facts []string
for _, result := range recallResp.Results {
facts = append(facts, result.Text)
}
// Reflect to generate an answer
reflectResp, err := client.Reflect(ctx, bankID, req.Query,
hindsight.WithReflectBudget(hindsight.BudgetMid),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Store this interaction as a new memory
interaction := fmt.Sprintf("User asked: %q\nAssistant answered: %s", req.Query, reflectResp.Text)
go func() {
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client.Retain(bgCtx, bankID, interaction,
hindsight.WithContext("Q&A interaction"),
)
}()
writeJSON(w, AskResponse{
Answer: reflectResp.Text,
Facts: facts,
})
}
// handleRecall returns raw memories for a user.
func handleRecall(w http.ResponseWriter, r *http.Request) {
userID := r.PathValue("userID")
query := r.URL.Query().Get("q")
if query == "" {
query = "What do you know?"
}
ctx := r.Context()
bankID := bankFor(userID)
resp, err := client.Recall(ctx, bankID, query,
hindsight.WithBudget(hindsight.BudgetHigh),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var results []RecallFact
for _, result := range resp.Results {
results = append(results, RecallFact{
Text: result.Text,
Type: result.Type.Or("unknown"),
})
}
writeJSON(w, RecallResponse{Results: results})
}
func handleHealth(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, map[string]string{"status": "ok"})
}
// --- Helpers ---
func bankFor(userID string) string {
return "user-" + strings.ToLower(userID)
}
func ensureBank(ctx context.Context, bankID, userID string) {
client.CreateBank(ctx, bankID,
hindsight.WithBankName(fmt.Sprintf("Memory for %s", userID)),
hindsight.WithMission("Developer knowledge assistant. Remember technologies, problems solved, and preferences."),
)
}
func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(v)
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
```
## Running
### 1. Start Hindsight
```bash
export OPENAI_API_KEY=your-key
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
ghcr.io/vectorize-io/hindsight:latest
```
### 2. Start the service
```bash
go run main.go
```
### 3. Try it out
```bash
# Teach it something
curl -s localhost:8080/learn -d '{
"user_id": "alice",
"content": "I am building a Go microservice that uses gRPC and PostgreSQL",
"tags": ["project"]
}' | jq .
# Teach it more
curl -s localhost:8080/learn -d '{
"user_id": "alice",
"content": "I solved a connection pooling issue by switching from pgx to pgxpool",
"tags": ["debugging"]
}'
curl -s localhost:8080/learn -d '{
"user_id": "alice",
"content": "I prefer structured logging with slog over zerolog",
"tags": ["preferences"]
}'
# Ask a question (uses recall + reflect)
curl -s localhost:8080/ask -d '{
"user_id": "alice",
"query": "What tech stack am I using?"
}' | jq .
# Raw recall
curl -s "localhost:8080/recall/alice?q=database" | jq .
```
## How It Works
1. **`/learn`** - Stores information using `Retain`. Each piece of info becomes searchable facts, entities, and relationships.
2. **`/ask`** - Two-phase retrieval:
- **Recall**: Finds relevant facts from the user's memory bank
- **Reflect**: Synthesizes a response using those facts plus disposition-aware reasoning
- The Q&A interaction itself is stored as a new memory (fire-and-forget goroutine)
3. **`/recall/{userID}`** - Direct access to raw recalled facts for debugging or building custom UIs.
## Key Patterns
### Per-User Isolation
Each user gets their own bank (`user-alice`, `user-bob`). Banks are created lazily on first interaction via `CreateBank` (idempotent - safe to call repeatedly).
### Fire-and-Forget Memory
The `/ask` handler stores the interaction in a background goroutine so the response isn't delayed by the retain call:
```go
go func() {
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client.Retain(bgCtx, bankID, interaction)
}()
```
### Tag-Based Scoping
Tags partition memories within a bank. Query only `debugging` memories, or only `preferences`:
```bash
# The /recall endpoint could be extended to support tag filtering:
curl "localhost:8080/recall/alice?q=issues&tags=debugging"
```
## Next Steps
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Core operations walkthrough
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Bulk data ingestion
- [Per-User Memory](/cookbook/recipes/per-user-memory) - The pattern in depth
- [Go SDK Reference](/sdks/go) - Full API documentation

View file

@ -86,6 +86,18 @@ Learn how to build with Hindsight through practical examples:
href: "/cookbook/recipes/study_buddy",
description: "Track study sessions, identify knowledge gaps, and get personalized review suggestions",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Go Quickstart",
href: "/cookbook/recipes/go-quickstart",
description: "Get started with the Go client: retain, recall, and reflect",
tags: { sdk: "hindsight-go", topic: "Quick Start" }
},
{
title: "Go Concurrent Pipeline",
href: "/cookbook/recipes/go-concurrent-pipeline",
description: "Build a concurrent memory ingestion pipeline with goroutines",
tags: { sdk: "hindsight-go", topic: "Learning" }
}
]}
/>
@ -140,6 +152,12 @@ Learn how to build with Hindsight through practical examples:
href: "/cookbook/applications/taste-ai",
description: "Personal food assistant with AI SDK v6 showcasing recall, mental models, and directives",
tags: { sdk: "@vectorize-io/hindsight-ai-sdk", topic: "Recommendation" }
},
{
title: "Go Memory-Augmented API",
href: "/cookbook/applications/go-memory-service",
description: "Go HTTP microservice with per-user memory banks for a developer knowledge assistant",
tags: { sdk: "hindsight-go", topic: "Learning" }
}
]}
/>

View file

@ -0,0 +1,272 @@
---
sidebar_position: 13
---
# Go Concurrent Pipeline
Build a concurrent memory ingestion pipeline in Go. This recipe demonstrates how to use Go's concurrency primitives with the Hindsight client to ingest large datasets efficiently, then query them with recall and reflect.
## The Problem
You have a large dataset (log files, chat transcripts, documents) that needs to be ingested into Hindsight. Sequential ingestion is slow. Go's goroutines make it straightforward to parallelize.
## Architecture
```
┌──────────┐
│ Source │
│ (files, │
│ API, DB) │
└────┬─────┘
┌────▼─────┐
│ Producer │ reads data, sends to channel
└────┬─────┘
┌──────────┼──────────┐
│ │ │
┌────▼───┐ ┌───▼────┐ ┌──▼─────┐
│Worker 1│ │Worker 2│ │Worker N│ concurrent RetainBatch
└────┬───┘ └───┬────┘ └──┬─────┘
│ │ │
└─────────┼─────────┘
┌─────▼─────┐
│ Hindsight │
│ API │
└───────────┘
```
## Prerequisites
Hindsight server running (see [Go Quickstart](/cookbook/recipes/go-quickstart) for setup).
## The Pipeline
```go
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"sync"
"sync/atomic"
"time"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
const (
batchSize = 10 // memories per batch
numWorkers = 4 // concurrent workers
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
bankID := "pipeline-demo"
// Create the bank
client.CreateBank(ctx, bankID,
hindsight.WithBankName("Pipeline Demo"),
hindsight.WithMission("Knowledge base ingested from documents"),
)
// Ingest from a file (one line = one memory)
if len(os.Args) < 2 {
fmt.Println("usage: pipeline <file>")
os.Exit(1)
}
start := time.Now()
count := ingest(ctx, client, bankID, os.Args[1])
elapsed := time.Since(start)
fmt.Printf("\nIngested %d memories in %s (%.1f/sec)\n",
count, elapsed, float64(count)/elapsed.Seconds())
// Query the ingested data
fmt.Println("\n--- Recall ---")
resp, _ := client.Recall(ctx, bankID, "What are the key topics?",
hindsight.WithBudget(hindsight.BudgetHigh),
)
for _, r := range resp.Results {
fmt.Printf(" - %s\n", r.Text)
}
fmt.Println("\n--- Reflect ---")
answer, _ := client.Reflect(ctx, bankID, "Summarize everything you know")
fmt.Println(answer.Text)
}
func ingest(ctx context.Context, client *hindsight.Client, bankID, filename string) int64 {
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// Channel for batches
batches := make(chan []hindsight.MemoryItem, numWorkers*2)
var ingested atomic.Int64
// Start workers
var wg sync.WaitGroup
for i := range numWorkers {
wg.Add(1)
go func(id int) {
defer wg.Done()
for batch := range batches {
_, err := client.RetainBatch(ctx, bankID, batch)
if err != nil {
log.Printf("worker %d: batch failed: %v", id, err)
continue
}
n := ingested.Add(int64(len(batch)))
if n%100 == 0 {
fmt.Printf(" ingested %d memories...\n", n)
}
}
}(i)
}
// Producer: read lines and batch them
scanner := bufio.NewScanner(f)
var batch []hindsight.MemoryItem
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
batch = append(batch, hindsight.MemoryItem{Content: line})
if len(batch) >= batchSize {
batches <- batch
batch = nil
}
}
// Flush remaining
if len(batch) > 0 {
batches <- batch
}
close(batches)
wg.Wait()
return ingested.Load()
}
```
## Running It
Create a sample data file:
```bash
cat > sample_data.txt << 'EOF'
The Go programming language was created at Google in 2007
Go's concurrency model is based on CSP (Communicating Sequential Processes)
Goroutines are lightweight threads managed by the Go runtime
Channels provide typed conduits for communication between goroutines
The sync package provides mutexes, wait groups, and other synchronization primitives
Go modules were introduced in Go 1.11 for dependency management
The context package provides cancellation and deadline propagation
Go compiles to a single static binary with no external dependencies
The standard library includes an HTTP server, JSON parser, and crypto packages
Go 1.18 introduced generics with type parameters
EOF
```
```bash
go run . sample_data.txt
```
## Adding Tags for Partitioning
For larger datasets, use tags to partition memories by source or topic:
```go
func ingestWithTags(ctx context.Context, client *hindsight.Client, bankID, source string, lines []string) {
var items []hindsight.MemoryItem
for _, line := range lines {
items = append(items, hindsight.MemoryItem{
Content: line,
Tags: []string{source},
})
}
_, err := client.RetainBatch(ctx, bankID, items,
hindsight.WithDocumentTags([]string{"bulk-import", source}),
)
if err != nil {
log.Printf("ingest %s failed: %v", source, err)
}
}
// Later, recall only from a specific source
resp, _ := client.Recall(ctx, bankID, "What are the key concepts?",
hindsight.WithRecallTags([]string{"go-docs"}),
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
)
```
## Async Ingestion
For very large datasets, use async mode to avoid waiting for fact extraction:
```go
// Async retain returns immediately; processing happens in background
_, err := client.RetainBatch(ctx, bankID, items,
hindsight.WithAsync(true),
)
// Check operation status via the ogen client
ogen := client.OgenClient()
ops, _ := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
BankID: bankID,
})
```
## Graceful Shutdown
Use `context.WithCancel` for clean cancellation:
```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// In a signal handler:
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
go func() {
<-sigCh
fmt.Println("\nShutting down gracefully...")
cancel()
}()
// Workers will stop when ctx is cancelled
```
## Performance Tips
| Tip | Why |
|-----|-----|
| Use `RetainBatch` over individual `Retain` | Fewer HTTP round trips |
| Set `batchSize` to 10-50 | Balances throughput vs. memory |
| Use 4-8 workers | Matches typical API concurrency limits |
| Use `WithAsync(true)` for bulk imports | Returns immediately, processes in background |
| Tag your data | Enables scoped recall without reingesting |
## Next Steps
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Basic retain, recall, reflect
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
- [Go SDK Reference](/sdks/go) - Full API reference

View file

@ -0,0 +1,216 @@
---
sidebar_position: 12
---
# Go Quickstart
Get started with the Hindsight Go client in under 5 minutes. This recipe covers the three core operations: **retain**, **recall**, and **reflect**.
## Prerequisites
Make sure you have Hindsight running. The easiest way is via Docker:
```bash
export OPENAI_API_KEY=your-key
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
ghcr.io/vectorize-io/hindsight:latest
```
- API: http://localhost:8888
- UI: http://localhost:9999
## Installation
```bash
go get github.com/vectorize-io/hindsight-client-go
```
## Connect to Hindsight
```go
package main
import (
"context"
"fmt"
"log"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
bankID := "go-quickstart"
```
## Retain: Store Information
The `Retain` operation pushes new memories into Hindsight. Behind the scenes, an LLM extracts key facts, temporal data, entities, and relationships.
```go
// Simple retain
_, err = client.Retain(ctx, bankID,
"Alice works at Google as a software engineer",
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Stored memory about Alice's job")
// Retain with context and timestamp
_, err = client.Retain(ctx, bankID,
"Alice got promoted to senior engineer",
hindsight.WithContext("career update"),
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Stored memory about Alice's promotion")
```
## Retain Batch: Store Multiple Memories
```go
items := []hindsight.MemoryItem{
{Content: "Bob is a data scientist who works with Alice"},
{Content: "Charlie manages the team and reports to the VP of Engineering"},
{Content: "The team is working on a recommendation engine using Go"},
}
resp, err := client.RetainBatch(ctx, bankID, items)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Stored %d memories\n", resp.ItemsCount)
```
## Recall: Retrieve Memories
`Recall` retrieves memories matching a query using four parallel strategies: semantic similarity, keyword matching, entity/relationship graph traversal, and temporal filtering.
```go
// Simple recall
results, err := client.Recall(ctx, bankID, "What does Alice do?")
if err != nil {
log.Fatal(err)
}
fmt.Println("\nMemories about Alice:")
for _, r := range results.Results {
fmt.Printf(" - %s\n", r.Text)
}
```
```go
// Recall with options
results, err = client.Recall(ctx, bankID, "Who works on the team?",
hindsight.WithBudget(hindsight.BudgetHigh),
hindsight.WithMaxTokens(2048),
hindsight.WithTypes([]string{"world"}),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("\nTeam memories (world facts only):")
for _, r := range results.Results {
fmt.Printf(" - [%s] %s\n", r.Type.Or("?"), r.Text)
}
```
## Reflect: Generate Insights
`Reflect` performs disposition-aware reasoning over stored memories. It retrieves relevant context, then uses an LLM to synthesize a response. Great for summarization, analysis, and Q&A.
```go
answer, err := client.Reflect(ctx, bankID,
"What should I know about this team?",
)
if err != nil {
log.Fatal(err)
}
fmt.Println("\nReflection:")
fmt.Println(answer.Text)
```
## Full Program
Here's the complete program:
```go
package main
import (
"context"
"fmt"
"log"
"time"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
bankID := "go-quickstart"
// 1. Store memories
client.Retain(ctx, bankID, "Alice works at Google as a software engineer")
client.Retain(ctx, bankID, "Alice got promoted to senior engineer",
hindsight.WithContext("career update"),
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
)
items := []hindsight.MemoryItem{
{Content: "Bob is a data scientist who works with Alice"},
{Content: "Charlie manages the team and reports to the VP of Engineering"},
{Content: "The team is working on a recommendation engine using Go"},
}
client.RetainBatch(ctx, bankID, items)
// 2. Recall memories
results, _ := client.Recall(ctx, bankID, "What does Alice do?")
fmt.Println("Memories about Alice:")
for _, r := range results.Results {
fmt.Printf(" - %s\n", r.Text)
}
// 3. Reflect
answer, _ := client.Reflect(ctx, bankID, "What should I know about this team?")
fmt.Printf("\nReflection:\n%s\n", answer.Text)
// 4. Cleanup
client.DeleteBank(ctx, bankID)
}
```
## Memory Types
Hindsight organizes memory into distinct networks:
| Type | Description | Example |
|------|-------------|---------|
| **World** | Facts about the world | "Alice works at Google" |
| **Experience** | Agent's own experiences | "I helped Alice debug her code" |
| **Observation** | Complex models from reflection | "Alice is a senior IC focused on ML" |
## Next Steps
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Build a concurrent ingestion pipeline
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
- [Go SDK Reference](/sdks/go) - Full API reference

View file

@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
---
# CLI Reference

View file

@ -1,5 +1,5 @@
---
sidebar_position: 4
sidebar_position: 5
---
# Embedded SDK (hindsight-embed)

View file

@ -0,0 +1,294 @@
---
sidebar_position: 3
---
# Go Client
Official Go client for the Hindsight API, built on [ogen](https://github.com/ogen-go/ogen) for strongly-typed code generation from the OpenAPI 3.1 spec.
## Installation
```bash
go get github.com/vectorize-io/hindsight-client-go
```
Requires Go 1.25+.
## Quick Start
```go
package main
import (
"context"
"fmt"
"log"
hindsight "github.com/vectorize-io/hindsight-client-go"
)
func main() {
client, err := hindsight.New("http://localhost:8888")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Retain a memory
client.Retain(ctx, "my-bank", "Alice works at Google")
// Recall memories
resp, _ := client.Recall(ctx, "my-bank", "What does Alice do?")
for _, r := range resp.Results {
fmt.Println(r.Text)
}
// Reflect - generate response with disposition
answer, _ := client.Reflect(ctx, "my-bank", "Tell me about Alice")
fmt.Println(answer.Text)
}
```
## Client Initialization
```go
import hindsight "github.com/vectorize-io/hindsight-client-go"
// Default client
client, err := hindsight.New("http://localhost:8888")
// With API key authentication
client, err := hindsight.New("http://localhost:8888",
hindsight.WithAPIKey("your-api-key"),
)
// With custom HTTP client
client, err := hindsight.New("http://localhost:8888",
hindsight.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
)
```
## Core Operations
### Retain (Store Memory)
```go
// Simple
_, err := client.Retain(ctx, "my-bank", "Alice works at Google as a software engineer")
// With options
_, err := client.Retain(ctx, "my-bank", "Alice got promoted",
hindsight.WithContext("career update"),
hindsight.WithTimestamp(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)),
hindsight.WithDocumentID("conversation_001"),
hindsight.WithMetadata(map[string]string{"source": "slack"}),
hindsight.WithTags([]string{"career", "updates"}),
)
```
### Retain Batch
```go
items := []hindsight.MemoryItem{
{Content: "Alice works at Google"},
{Content: "Bob is a data scientist"},
}
_, err := client.RetainBatch(ctx, "my-bank", items,
hindsight.WithDocumentTags([]string{"team-info"}),
hindsight.WithAsync(false), // Set true for background processing
)
```
### Recall (Search)
```go
// Simple
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?")
for _, r := range resp.Results {
fmt.Printf(" %s (type: %s)\n", r.Text, r.Type.Or("unknown"))
}
// With options
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?",
hindsight.WithTypes([]string{"world", "experience"}), // Filter by fact type
hindsight.WithMaxTokens(4096),
hindsight.WithBudget(hindsight.BudgetHigh), // BudgetLow, BudgetMid, BudgetHigh
hindsight.WithTrace(true), // Include execution trace
hindsight.WithRecallTags([]string{"career"}),
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
)
```
### Reflect (Generate Response)
```go
resp, err := client.Reflect(ctx, "my-bank", "What should I know about Alice?",
hindsight.WithReflectBudget(hindsight.BudgetMid),
hindsight.WithReflectMaxTokens(2048),
)
fmt.Println(resp.Text) // Generated markdown response
```
### Reflect with Structured Output
```go
resp, err := client.Reflect(ctx, "my-bank",
"What programming language should I learn for data science?",
hindsight.WithResponseSchema(map[string]any{
"type": "object",
"properties": map[string]any{
"recommendation": map[string]any{"type": "string"},
"reasons": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
"confidence": map[string]any{"type": "string"},
},
"required": []any{"recommendation", "reasons"},
}),
hindsight.WithReflectMaxTokens(4096),
)
// resp.StructuredOutput contains the parsed JSON schema result
```
## Bank Management
### Create Bank
```go
_, err := client.CreateBank(ctx, "my-bank",
hindsight.WithBankName("Assistant"),
hindsight.WithMission("Helpful AI assistant tracking user preferences."),
hindsight.WithDisposition(hindsight.DispositionTraits{
Skepticism: 3, // 1-5: trusting to skeptical
Literalism: 3, // 1-5: flexible to literal
Empathy: 3, // 1-5: detached to empathetic
}),
)
```
### Other Bank Operations
```go
// Get bank profile
profile, err := client.GetBankProfile(ctx, "my-bank")
fmt.Println(profile.Mission)
// List all banks
banks, err := client.ListBanks(ctx)
for _, b := range banks.Banks {
fmt.Println(b.BankID)
}
// Update mission
_, err = client.SetMission(ctx, "my-bank", "New mission statement")
// Update disposition
_, err = client.UpdateDisposition(ctx, "my-bank", hindsight.DispositionTraits{
Skepticism: 4,
Literalism: 2,
Empathy: 5,
})
// Delete bank (destructive, cannot be undone)
err = client.DeleteBank(ctx, "my-bank")
```
## Tag Filtering
Tags provide visibility scoping for memories. Use them to partition memories within a bank.
```go
// Store tagged memories
client.Retain(ctx, "my-bank", "Project X meeting notes",
hindsight.WithTags([]string{"project_x", "meetings"}),
)
// Recall only project_x memories (strict - excludes untagged)
resp, _ := client.Recall(ctx, "my-bank", "What happened in meetings?",
hindsight.WithRecallTags([]string{"project_x"}),
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
)
// Reflect scoped to tags
resp, _ := client.Reflect(ctx, "my-bank", "Summarize project X",
hindsight.WithReflectTags([]string{"project_x"}),
hindsight.WithReflectTagsMatch(hindsight.TagsMatchAnyStrict),
)
```
| Match Mode | Behavior |
|-----------|----------|
| `TagsMatchAny` | OR matching, includes untagged memories |
| `TagsMatchAll` | AND matching, includes untagged memories |
| `TagsMatchAnyStrict` | OR matching, excludes untagged memories |
| `TagsMatchAllStrict` | AND matching, excludes untagged memories |
## Advanced Usage (ogen Client)
For operations not covered by the high-level wrapper (documents, entities, mental models, directives, operations), access the generated ogen client directly:
```go
import "github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
ogen := client.OgenClient()
// List entities
entities, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{
BankID: "my-bank",
})
// Create a mental model
model, err := ogen.CreateMentalModel(ctx,
&ogenapi.CreateMentalModelRequest{
Name: ogenapi.NewOptString("User Preferences"),
SourceQuery: "What are this user's preferences and habits?",
},
ogenapi.CreateMentalModelParams{BankID: "my-bank"},
)
// Create a directive
directive, err := ogen.CreateDirective(ctx,
&ogenapi.CreateDirectiveRequest{
Name: "Response Style",
Content: "Always respond in a friendly, concise manner",
},
ogenapi.CreateDirectiveParams{BankID: "my-bank"},
)
// List operations (async tasks)
ops, err := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
BankID: "my-bank",
})
// Get bank stats
stats, err := ogen.GetAgentStats(ctx, ogenapi.GetAgentStatsParams{
BankID: "my-bank",
})
```
## Error Handling
The client returns standard Go errors. HTTP errors from the API are returned as ogen error types:
```go
resp, err := client.Recall(ctx, "nonexistent-bank", "query")
if err != nil {
// Handle error - could be network, HTTP 4xx/5xx, etc.
log.Printf("recall failed: %v", err)
}
```
## Code Generation
The Go client is built on [ogen](https://github.com/ogen-go/ogen). The generated code lives in `internal/ogenapi/` and provides full type safety with no `interface{}` or reflection.
To regenerate after API changes:
```bash
cd hindsight-clients/go
go generate ./...
go build ./...
```

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
# Script to generate Python and TypeScript clients from OpenAPI spec using openapi-generator
# Script to generate Python, TypeScript, and Go clients from OpenAPI spec
# Note: Rust client is auto-generated at build time via build.rs (uses progenitor)
# Usage: ./scripts/generate-clients.sh
@ -24,6 +24,7 @@ echo "This script generates clients for:"
echo " - Rust (via progenitor in build.rs)"
echo " - Python (via openapi-generator)"
echo " - TypeScript (via @hey-api/openapi-ts)"
echo " - Go (via ogen)"
echo ""
# Check if OpenAPI spec exists
@ -333,6 +334,25 @@ npm run generate
echo "✓ TypeScript client generated at $TYPESCRIPT_CLIENT_DIR"
echo ""
# Generate Go client
echo "=================================================="
echo "Generating Go client..."
echo "=================================================="
GO_CLIENT_DIR="$CLIENTS_DIR/go"
if ! command -v go &> /dev/null; then
echo "⚠ Go not found, skipping Go client generation"
echo " Install Go 1.25+ from https://go.dev/dl/"
else
echo "Regenerating Go client (via ogen)..."
cd "$GO_CLIENT_DIR"
go generate ./...
go build ./...
echo "✓ Go client generated at $GO_CLIENT_DIR"
fi
echo ""
echo "=================================================="
echo "✅ Client generation complete!"
echo "=================================================="
@ -340,6 +360,7 @@ echo ""
echo "Rust client: $RUST_CLIENT_DIR"
echo "Python client: $PYTHON_CLIENT_DIR"
echo "TypeScript client: $TYPESCRIPT_CLIENT_DIR"
echo "Go client: $GO_CLIENT_DIR"
echo ""
echo "⚠️ Important: The maintained wrapper hindsight_client.py and README.md were preserved"
echo ""