Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions recalled_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "encoding/json"
// The Dakera server returns recall/search results with memory fields nested
// under a "memory" key and score at the top level:
//
// {"memory": {"id": "...", "content": "...", ...}, "score": 0.95}
// {"memory": {"id": "...", "content": "...", ...}, "score": 0.95, "smart_score": 0.87}
//
// The flat fields (ID, Content, …) are retained so that the flat format used
// in unit test mocks and by the /v1/agents/{id}/memories endpoint continues to
Expand All @@ -17,8 +17,10 @@ type recalledMemoryWire struct {
Memory *Memory `json:"memory"`

// Top-level fields present in all formats
Score float32 `json:"score"`
Depth *int `json:"depth,omitempty"`
Score float32 `json:"score"`
WeightedScore *float32 `json:"weighted_score,omitempty"`
SmartScore *float32 `json:"smart_score,omitempty"`
Depth *int `json:"depth,omitempty"`

// Flat fields — populated when the "memory" wrapper is absent
ID string `json:"id"`
Expand All @@ -33,7 +35,7 @@ type recalledMemoryWire struct {
// UnmarshalJSON implements json.Unmarshaler for RecalledMemory.
//
// Handles two wire formats:
// - Nested (server): {"memory": {"id": "...", "content": "..."}, "score": N}
// - Nested (server): {"memory": {"id": "...", "content": "..."}, "score": N, "smart_score": N}
// - Flat (unit tests, /v1/agents/{id}/memories): {"id": "...", "content": "...", "score": N}
func (r *RecalledMemory) UnmarshalJSON(data []byte) error {
var wire recalledMemoryWire
Expand Down Expand Up @@ -61,7 +63,17 @@ func (r *RecalledMemory) UnmarshalJSON(data []byte) error {
r.CreatedAt = wire.CreatedAt
}

r.Score = wire.Score
// smart_score is the server's ranking key; prefer it over weighted_score and raw score.
r.SmartScore = wire.SmartScore
r.WeightedScore = wire.WeightedScore
switch {
case wire.SmartScore != nil:
r.Score = *wire.SmartScore
case wire.WeightedScore != nil:
r.Score = *wire.WeightedScore
default:
r.Score = wire.Score
}
r.Depth = wire.Depth
return nil
}
82 changes: 82 additions & 0 deletions recalled_memory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package dakera

import (
"encoding/json"
"testing"
)

func ptr32(v float32) *float32 { return &v }

func TestRecalledMemory_SmartScorePriority(t *testing.T) {
data := []byte(`{
"memory": {"id": "m1", "content": "test", "memory_type": "episodic", "importance": 0.8},
"score": 0.5,
"weighted_score": 0.7,
"smart_score": 0.9
}`)
var m RecalledMemory
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if m.Score != 0.9 {
t.Errorf("Score = %v, want 0.9 (smart_score priority)", m.Score)
}
if m.SmartScore == nil || *m.SmartScore != 0.9 {
t.Errorf("SmartScore = %v, want &0.9", m.SmartScore)
}
if m.WeightedScore == nil || *m.WeightedScore != 0.7 {
t.Errorf("WeightedScore = %v, want &0.7", m.WeightedScore)
}
}

func TestRecalledMemory_WeightedScoreFallback(t *testing.T) {
data := []byte(`{
"memory": {"id": "m2", "content": "test", "memory_type": "episodic", "importance": 0.8},
"score": 0.5,
"weighted_score": 0.7
}`)
var m RecalledMemory
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if m.Score != 0.7 {
t.Errorf("Score = %v, want 0.7 (weighted_score fallback)", m.Score)
}
if m.SmartScore != nil {
t.Errorf("SmartScore = %v, want nil", m.SmartScore)
}
}

func TestRecalledMemory_RawScoreFallback(t *testing.T) {
data := []byte(`{
"memory": {"id": "m3", "content": "test", "memory_type": "episodic", "importance": 0.8},
"score": 0.55
}`)
var m RecalledMemory
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if m.Score != 0.55 {
t.Errorf("Score = %v, want 0.55 (raw score fallback)", m.Score)
}
if m.SmartScore != nil {
t.Errorf("SmartScore = %v, want nil", m.SmartScore)
}
if m.WeightedScore != nil {
t.Errorf("WeightedScore = %v, want nil", m.WeightedScore)
}
}

func TestRecalledMemory_FlatFormatScore(t *testing.T) {
data := []byte(`{"id": "m4", "content": "flat", "memory_type": "episodic", "importance": 0.5, "score": 0.6}`)
var m RecalledMemory
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if m.Score != 0.6 {
t.Errorf("Score = %v, want 0.6 (flat format)", m.Score)
}
if m.SmartScore != nil {
t.Errorf("SmartScore = %v, want nil for flat format", m.SmartScore)
}
}
16 changes: 11 additions & 5 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,18 +518,24 @@ type Memory struct {
// RecalledMemory represents a recalled memory with similarity score.
//
// The server wraps memory fields under a nested "memory" key with score at the
// top level: {"memory": {"id": "...", "content": "...", ...}, "score": 0.95}.
// top level: {"memory": {"id": "...", "content": "...", ...}, "score": 0.95, "smart_score": 0.87}.
// UnmarshalJSON handles this nested wire format transparently so callers always
// see populated ID, Content, and other fields.
//
// Score is set to SmartScore when present, then WeightedScore, then raw Score —
// matching the server's ranking key so results appear in true rank order.
type RecalledMemory struct {
ID string `json:"id"`
Content string `json:"content"`
MemoryType string `json:"memory_type"`
Importance float32 `json:"importance"`
Score float32 `json:"score"`
Tags []string `json:"tags,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
// Ranking score — equals SmartScore when present, then WeightedScore, then raw score.
Score float32 `json:"score"`
SmartScore *float32 `json:"smart_score,omitempty"`
WeightedScore *float32 `json:"weighted_score,omitempty"`
Tags []string `json:"tags,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
// KG-3: hop depth at which this memory was found (only set on associated memories)
Depth *int `json:"depth,omitempty"`
}
Expand Down
Loading