feat(memory): add kernel contract, DAG snapshot persistence, and map_ops schema
pkg/memory/kernel_contract.go - Defines the MemoryKernelContract interface for active context projection: ActiveContext(), ProjectSession(), SyncProjection(), RecoverSession() - Provides a deterministic contract for lossless session continuity with DAG-based compression and projection pointer tracking pkg/memory/dag/store.go - DAGStore: persistent snapshot storage for DAG nodes and edges in SQLite - Stores compression snapshots with provenance metadata (origin session, message range, token counts) - Supports node/edge insertion, snapshot listing, and node lookup by ID pkg/memory/dag/backfill.go + backfill_test.go - BackfillDAGFromSession(): builds a DAG snapshot from an existing session history for sessions that pre-date DAG compression - Idempotent: skips sessions that already have a snapshot pkg/memory/migrations/011_dag_tables.go - SQL migration adding dag_snapshots, dag_nodes, and dag_edges tables - Includes indexes for snapshot_id, session_key, and node type lookups pkg/memory/migrations/012_map_operator_runs.go - SQL migration adding map_operator_runs and map_operator_items tables - Tracks batch map operation runs with per-item status and FlatBuffers serialized payloads pkg/memory/sqlc/queries/dag.sql + dag.sql.go - SQLC queries for DAG snapshot CRUD operations pkg/memory/sqlc/queries/map_ops.sql + map_ops.sql.go - SQLC queries for map operator run and item tracking pkg/memory/store/retrieval_policy.go - RetrievalPolicy: configures token budget, recency bias, and semantic score thresholds for memory retrieval operations - Used by the kernel contract to bound context window usage pkg/memory/delegate/sqlite_dag_test.go - Integration tests for DAG table creation, snapshot insertion, and node/edge persistence through the SQLite delegate
This commit is contained in:
parent
db4d98a1d4
commit
1a433dc596
14 changed files with 3026 additions and 0 deletions
236
pkg/memory/dag/backfill.go
Normal file
236
pkg/memory/dag/backfill.go
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
package dag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
|
||||||
|
memsqlc "github.com/ZanzyTHEbar/dragonscale/pkg/memory/sqlc"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dagBackfillStatusKVKey = "migration:dag_backfill:v1"
|
||||||
|
|
||||||
|
// BackfillOptions controls DAG backfill behavior.
|
||||||
|
type BackfillOptions struct {
|
||||||
|
PageSize int
|
||||||
|
MaxSessions int
|
||||||
|
Force bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultBackfillOptions() BackfillOptions {
|
||||||
|
return BackfillOptions{
|
||||||
|
PageSize: 500,
|
||||||
|
MaxSessions: 0,
|
||||||
|
Force: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackfillStatus tracks one DAG backfill pass over existing sessions.
|
||||||
|
type BackfillStatus struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
SessionsScanned int `json:"sessions_scanned"`
|
||||||
|
SnapshotsCreated int `json:"snapshots_created"`
|
||||||
|
SkippedExisting int `json:"skipped_existing"`
|
||||||
|
Failures int `json:"failures"`
|
||||||
|
CompletedAt time.Time `json:"completed_at"`
|
||||||
|
Skipped bool `json:"skipped,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackfillMissingSessionDAGs creates DAG snapshots for sessions that have
|
||||||
|
// session-message history but no persisted DAG snapshot yet.
|
||||||
|
//
|
||||||
|
// The pass is one-shot by default and records status in KV under
|
||||||
|
// dagBackfillStatusKVKey. Use opts.Force=true to run again.
|
||||||
|
func BackfillMissingSessionDAGs(
|
||||||
|
ctx context.Context,
|
||||||
|
delegate memory.MemoryDelegate,
|
||||||
|
queries *memsqlc.Queries,
|
||||||
|
agentID string,
|
||||||
|
opts BackfillOptions,
|
||||||
|
) (*BackfillStatus, error) {
|
||||||
|
if delegate == nil || queries == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
persister, ok := delegate.(DAGPersister)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.PageSize <= 0 {
|
||||||
|
opts.PageSize = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Force {
|
||||||
|
if raw, err := delegate.GetKV(ctx, agentID, dagBackfillStatusKVKey); err == nil && strings.TrimSpace(raw) != "" {
|
||||||
|
var status BackfillStatus
|
||||||
|
if uErr := jsonv2.Unmarshal([]byte(raw), &status); uErr == nil {
|
||||||
|
status.Skipped = true
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
return &BackfillStatus{Version: 1, Skipped: true}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKeys, err := collectSessionKeysForBackfill(ctx, delegate, agentID, opts.PageSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if opts.MaxSessions > 0 && len(sessionKeys) > opts.MaxSessions {
|
||||||
|
sessionKeys = sessionKeys[:opts.MaxSessions]
|
||||||
|
}
|
||||||
|
|
||||||
|
status := &BackfillStatus{
|
||||||
|
Version: 1,
|
||||||
|
SessionsScanned: len(sessionKeys),
|
||||||
|
}
|
||||||
|
compressor := NewCompressor(DefaultCompressorConfig())
|
||||||
|
|
||||||
|
for _, sessionKey := range sessionKeys {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return status, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := queries.GetLatestDAGSnapshotBySession(ctx, memsqlc.GetLatestDAGSnapshotBySessionParams{
|
||||||
|
AgentID: agentID,
|
||||||
|
SessionKey: sessionKey,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
status.SkippedExisting++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
status.Failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, err := loadSessionMessagesForBackfill(ctx, delegate, agentID, sessionKey, opts.PageSize)
|
||||||
|
if err != nil {
|
||||||
|
status.Failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := compressor.Compress(msgs)
|
||||||
|
if d == nil || len(d.Nodes) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := persister.PersistDAG(ctx, agentID, sessionKey, &PersistSnapshot{
|
||||||
|
FromMsgIdx: 0,
|
||||||
|
ToMsgIdx: len(msgs),
|
||||||
|
MsgCount: len(msgs),
|
||||||
|
DAG: d,
|
||||||
|
}); err != nil {
|
||||||
|
status.Failures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
status.SnapshotsCreated++
|
||||||
|
}
|
||||||
|
|
||||||
|
status.CompletedAt = time.Now().UTC()
|
||||||
|
data, err := jsonv2.Marshal(status)
|
||||||
|
if err != nil {
|
||||||
|
return status, err
|
||||||
|
}
|
||||||
|
if err := delegate.UpsertKV(ctx, agentID, dagBackfillStatusKVKey, string(data)); err != nil {
|
||||||
|
return status, err
|
||||||
|
}
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectSessionKeysForBackfill(ctx context.Context, delegate memory.MemoryDelegate, agentID string, pageSize int) ([]string, error) {
|
||||||
|
keys := make(map[string]struct{})
|
||||||
|
offset := 0
|
||||||
|
for {
|
||||||
|
items, err := delegate.ListRecallItems(ctx, agentID, "", pageSize, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(items) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
if !strings.Contains(item.Tags, "session-message") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys[item.SessionKey] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(items) < pageSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += len(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]string, 0, len(keys))
|
||||||
|
for k := range keys {
|
||||||
|
out = append(out, k)
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSessionMessagesForBackfill(
|
||||||
|
ctx context.Context,
|
||||||
|
delegate memory.MemoryDelegate,
|
||||||
|
agentID string,
|
||||||
|
sessionKey string,
|
||||||
|
pageSize int,
|
||||||
|
) ([]Message, error) {
|
||||||
|
type sessionLister interface {
|
||||||
|
ListSessionMessages(ctx context.Context, agentID, sessionKey, role string, limit int) ([]*memory.RecallItem, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lister, ok := delegate.(sessionLister); ok {
|
||||||
|
count, err := delegate.CountRecallItems(ctx, agentID, sessionKey)
|
||||||
|
if err != nil {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
limit := count + 32
|
||||||
|
if limit < 32 {
|
||||||
|
limit = 32
|
||||||
|
}
|
||||||
|
rows, err := lister.ListSessionMessages(ctx, agentID, sessionKey, "", limit)
|
||||||
|
if err == nil {
|
||||||
|
msgs := make([]Message, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
msgs = append(msgs, Message{Role: row.Role, Content: row.Content})
|
||||||
|
}
|
||||||
|
return msgs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var all []*memory.RecallItem
|
||||||
|
offset := 0
|
||||||
|
for {
|
||||||
|
items, err := delegate.ListRecallItems(ctx, agentID, sessionKey, pageSize, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(items) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
all = append(all, items...)
|
||||||
|
if len(items) < pageSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += len(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := make([]Message, 0, len(all))
|
||||||
|
for i := len(all) - 1; i >= 0; i-- {
|
||||||
|
item := all[i]
|
||||||
|
if !strings.Contains(item.Tags, "session-message") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msgs = append(msgs, Message{Role: item.Role, Content: item.Content})
|
||||||
|
}
|
||||||
|
return msgs, nil
|
||||||
|
}
|
||||||
79
pkg/memory/dag/backfill_test.go
Normal file
79
pkg/memory/dag/backfill_test.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package dag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/dag"
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/delegate"
|
||||||
|
memsqlc "github.com/ZanzyTHEbar/dragonscale/pkg/memory/sqlc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBackfillMissingSessionDAGs_CreatesSnapshotsAndPersistsStatus(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
d, err := delegate.NewLibSQLInMemory()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, d.Init(ctx))
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
agentID := "agent-backfill"
|
||||||
|
sessionKey := "legacy-session-1"
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
role := "user"
|
||||||
|
if i%2 == 1 {
|
||||||
|
role = "assistant"
|
||||||
|
}
|
||||||
|
require.NoError(t, d.InsertRecallItem(ctx, &memory.RecallItem{
|
||||||
|
ID: ids.New(),
|
||||||
|
AgentID: agentID,
|
||||||
|
SessionKey: sessionKey,
|
||||||
|
Role: role,
|
||||||
|
Sector: memory.SectorEpisodic,
|
||||||
|
Importance: 0.5,
|
||||||
|
Salience: 0.5,
|
||||||
|
DecayRate: 0.01,
|
||||||
|
Content: fmt.Sprintf("legacy message %d", i),
|
||||||
|
Tags: "session-message",
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := dag.BackfillMissingSessionDAGs(ctx, d, d.Queries(), agentID, dag.DefaultBackfillOptions())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, status)
|
||||||
|
assert.Equal(t, 1, status.SnapshotsCreated)
|
||||||
|
assert.Equal(t, 0, status.Failures)
|
||||||
|
|
||||||
|
row, err := d.Queries().GetLatestDAGSnapshotBySession(ctx, memsqlc.GetLatestDAGSnapshotBySessionParams{
|
||||||
|
AgentID: agentID,
|
||||||
|
SessionKey: sessionKey,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(12), row.MsgCount)
|
||||||
|
|
||||||
|
kv, err := d.ListKVByPrefix(ctx, agentID, "migration:dag_backfill", 10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, kv)
|
||||||
|
|
||||||
|
var stored dag.BackfillStatus
|
||||||
|
for _, raw := range kv {
|
||||||
|
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &stored))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
assert.Equal(t, status.SnapshotsCreated, stored.SnapshotsCreated)
|
||||||
|
assert.False(t, stored.CompletedAt.IsZero())
|
||||||
|
|
||||||
|
status2, err := dag.BackfillMissingSessionDAGs(ctx, d, d.Queries(), agentID, dag.DefaultBackfillOptions())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, status2)
|
||||||
|
assert.True(t, status2.Skipped)
|
||||||
|
}
|
||||||
166
pkg/memory/dag/store.go
Normal file
166
pkg/memory/dag/store.go
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
package dag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
memsqlc "github.com/ZanzyTHEbar/dragonscale/pkg/memory/sqlc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PersistSnapshot is the serializable shape for persisting a DAG snapshot.
|
||||||
|
type PersistSnapshot struct {
|
||||||
|
FromMsgIdx int
|
||||||
|
ToMsgIdx int
|
||||||
|
MsgCount int
|
||||||
|
DAG *DAG
|
||||||
|
}
|
||||||
|
|
||||||
|
// contentHash returns a deterministic hash of the DAG structure for deduplication.
|
||||||
|
func contentHash(d *DAG) string {
|
||||||
|
if d == nil || len(d.Nodes) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
h := sha256.New()
|
||||||
|
roots, _ := json.Marshal(d.Roots)
|
||||||
|
h.Write(roots)
|
||||||
|
|
||||||
|
nodeIDs := make([]string, 0, len(d.Nodes))
|
||||||
|
for id := range d.Nodes {
|
||||||
|
nodeIDs = append(nodeIDs, id)
|
||||||
|
}
|
||||||
|
sort.Strings(nodeIDs)
|
||||||
|
|
||||||
|
for _, id := range nodeIDs {
|
||||||
|
n := d.Nodes[id]
|
||||||
|
h.Write([]byte(id))
|
||||||
|
h.Write([]byte(n.Summary))
|
||||||
|
h.Write([]byte(fmt.Sprintf("%d:%d:%d", n.StartIdx, n.EndIdx, n.Tokens)))
|
||||||
|
if len(n.Children) > 0 {
|
||||||
|
children := append([]string(nil), n.Children...)
|
||||||
|
sort.Strings(children)
|
||||||
|
for _, child := range children {
|
||||||
|
h.Write([]byte(child))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DAGPersister persists DAG snapshots to storage.
|
||||||
|
// Implemented by delegates that support DAG persistence.
|
||||||
|
type DAGPersister interface {
|
||||||
|
PersistDAG(ctx context.Context, agentID, sessionKey string, snap *PersistSnapshot) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersistDAG persists a DAG snapshot via sqlc in a transaction.
|
||||||
|
func PersistDAG(ctx context.Context, db *sql.DB, q *memsqlc.Queries, agentID, sessionKey string, snap *PersistSnapshot) error {
|
||||||
|
if snap == nil || snap.DAG == nil || len(snap.DAG.Nodes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := contentHash(snap.DAG)
|
||||||
|
snapshotID := ids.New()
|
||||||
|
|
||||||
|
rootsJSON, err := json.Marshal(snap.DAG.Roots)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal roots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("begin tx: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
qTx := q.WithTx(tx)
|
||||||
|
|
||||||
|
_, err = qTx.InsertDAGSnapshot(ctx, memsqlc.InsertDAGSnapshotParams{
|
||||||
|
ID: snapshotID,
|
||||||
|
AgentID: agentID,
|
||||||
|
SessionKey: sessionKey,
|
||||||
|
FromMsgIdx: int64(snap.FromMsgIdx),
|
||||||
|
ToMsgIdx: int64(snap.ToMsgIdx),
|
||||||
|
MsgCount: int64(snap.MsgCount),
|
||||||
|
RootsJson: string(rootsJSON),
|
||||||
|
ContentHash: hash,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert snapshot: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIDsOrdered := make([]string, 0, len(snap.DAG.Nodes))
|
||||||
|
for nodeID := range snap.DAG.Nodes {
|
||||||
|
nodeIDsOrdered = append(nodeIDsOrdered, nodeID)
|
||||||
|
}
|
||||||
|
sort.Strings(nodeIDsOrdered)
|
||||||
|
|
||||||
|
nodeIDs := make(map[string]ids.UUID)
|
||||||
|
for _, nodeID := range nodeIDsOrdered {
|
||||||
|
node := snap.DAG.Nodes[nodeID]
|
||||||
|
nodeUUID := ids.New()
|
||||||
|
nodeIDs[nodeID] = nodeUUID
|
||||||
|
|
||||||
|
nodeHash := sha256.Sum256([]byte(fmt.Sprintf("%s|%s|%d|%d|%d", node.ID, node.Summary, node.StartIdx, node.EndIdx, node.Tokens)))
|
||||||
|
|
||||||
|
metricsJSONBytes, _ := json.Marshal(map[string]any{
|
||||||
|
"tokens": node.Tokens,
|
||||||
|
"span": node.Span(),
|
||||||
|
"children_count": len(node.Children),
|
||||||
|
})
|
||||||
|
metadataJSONBytes, _ := json.Marshal(map[string]any{
|
||||||
|
"level": node.Level.String(),
|
||||||
|
"children": node.Children,
|
||||||
|
})
|
||||||
|
_, err = qTx.InsertDAGNode(ctx, memsqlc.InsertDAGNodeParams{
|
||||||
|
ID: nodeUUID,
|
||||||
|
SnapshotID: snapshotID,
|
||||||
|
NodeID: node.ID,
|
||||||
|
Level: int64(node.Level),
|
||||||
|
Summary: node.Summary,
|
||||||
|
Tokens: int64(node.Tokens),
|
||||||
|
StartIdx: int64(node.StartIdx),
|
||||||
|
EndIdx: int64(node.EndIdx),
|
||||||
|
Span: int64(node.Span()),
|
||||||
|
ContentHash: hex.EncodeToString(nodeHash[:]),
|
||||||
|
MetricsJson: metricsJSONBytes,
|
||||||
|
MetadataJson: metadataJSONBytes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert node %s: %w", node.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nodeID := range nodeIDsOrdered {
|
||||||
|
node := snap.DAG.Nodes[nodeID]
|
||||||
|
for edgeIdx, childID := range node.Children {
|
||||||
|
childUUID, ok := nodeIDs[childID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parentUUID := nodeIDs[node.ID]
|
||||||
|
edgeMetadata, _ := json.Marshal(map[string]any{
|
||||||
|
"parent_node_id": node.ID,
|
||||||
|
"child_node_id": childID,
|
||||||
|
})
|
||||||
|
err = qTx.InsertDAGEdge(ctx, memsqlc.InsertDAGEdgeParams{
|
||||||
|
ID: ids.New(),
|
||||||
|
SnapshotID: snapshotID,
|
||||||
|
ParentNodeID: parentUUID,
|
||||||
|
ChildNodeID: childUUID,
|
||||||
|
EdgeIndex: int64(edgeIdx),
|
||||||
|
MetadataJson: edgeMetadata,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert edge %s->%s: %w", node.ID, childID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
50
pkg/memory/delegate/sqlite_dag_test.go
Normal file
50
pkg/memory/delegate/sqlite_dag_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package delegate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory/dag"
|
||||||
|
memsqlc "github.com/ZanzyTHEbar/dragonscale/pkg/memory/sqlc"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLibSQLDelegate_PersistDAG(t *testing.T) {
|
||||||
|
d := newTestDelegate(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
compressor := dag.NewCompressor(dag.DefaultCompressorConfig())
|
||||||
|
msgs := make([]dag.Message, 16)
|
||||||
|
for i := range msgs {
|
||||||
|
role := "user"
|
||||||
|
if i%2 == 1 {
|
||||||
|
role = "assistant"
|
||||||
|
}
|
||||||
|
msgs[i] = dag.Message{Role: role, Content: "msg " + string(rune('A'+i%26))}
|
||||||
|
}
|
||||||
|
dagOut := compressor.Compress(msgs)
|
||||||
|
require.NotEmpty(t, dagOut.Nodes)
|
||||||
|
|
||||||
|
snap := &dag.PersistSnapshot{
|
||||||
|
FromMsgIdx: 0,
|
||||||
|
ToMsgIdx: 16,
|
||||||
|
MsgCount: 16,
|
||||||
|
DAG: dagOut,
|
||||||
|
}
|
||||||
|
require.NoError(t, d.PersistDAG(ctx, "agent1", "session1", snap))
|
||||||
|
|
||||||
|
row, err := d.Queries().GetLatestDAGSnapshotBySession(ctx, memsqlc.GetLatestDAGSnapshotBySessionParams{
|
||||||
|
AgentID: "agent1",
|
||||||
|
SessionKey: "session1",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "agent1", row.AgentID)
|
||||||
|
require.Equal(t, "session1", row.SessionKey)
|
||||||
|
require.Equal(t, int64(16), row.MsgCount)
|
||||||
|
|
||||||
|
nodes, err := d.Queries().ListDAGNodesBySnapshotID(ctx, memsqlc.ListDAGNodesBySnapshotIDParams{
|
||||||
|
SnapshotID: row.ID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, nodes)
|
||||||
|
}
|
||||||
95
pkg/memory/kernel_contract.go
Normal file
95
pkg/memory/kernel_contract.go
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProjectionSegmentKind classifies what role a segment plays in active context.
|
||||||
|
type ProjectionSegmentKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProjectionSegmentSystem ProjectionSegmentKind = "system"
|
||||||
|
ProjectionSegmentRecent ProjectionSegmentKind = "recent"
|
||||||
|
ProjectionSegmentDAG ProjectionSegmentKind = "dag"
|
||||||
|
ProjectionSegmentRecall ProjectionSegmentKind = "recall"
|
||||||
|
ProjectionSegmentArchival ProjectionSegmentKind = "archival"
|
||||||
|
ProjectionSegmentTool ProjectionSegmentKind = "tool_result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImmutableSpanRef is a lossless reference back into immutable session history.
|
||||||
|
type ImmutableSpanRef struct {
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
StartIdx int `json:"start_idx"` // inclusive
|
||||||
|
EndIdx int `json:"end_idx"` // exclusive
|
||||||
|
FirstID ids.UUID `json:"first_id"`
|
||||||
|
LastID ids.UUID `json:"last_id"`
|
||||||
|
FromTime time.Time `json:"from_time"`
|
||||||
|
ToTime time.Time `json:"to_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectionSegment is one unit included in active context.
|
||||||
|
// Every segment must carry a lossless reference back to immutable history.
|
||||||
|
type ProjectionSegment struct {
|
||||||
|
Kind ProjectionSegmentKind `json:"kind"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Tokens int `json:"tokens"`
|
||||||
|
Ref ImmutableSpanRef `json:"ref"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveContextProjection is the assembled view injected into the model for a turn.
|
||||||
|
// It is a materialized projection, not a source of truth.
|
||||||
|
type ActiveContextProjection struct {
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
BudgetTokens int `json:"budget_tokens"`
|
||||||
|
GeneratedAt time.Time `json:"generated_at"`
|
||||||
|
ProjectionRef string `json:"projection_ref"`
|
||||||
|
Segments []ProjectionSegment `json:"segments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ActiveContextProjection) TotalTokens() int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
total := 0
|
||||||
|
for _, seg := range p.Segments {
|
||||||
|
total += seg.Tokens
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasLosslessRefs verifies the projection preserves deterministic pointers to
|
||||||
|
// immutable history for every segment.
|
||||||
|
func (p *ActiveContextProjection) HasLosslessRefs() bool {
|
||||||
|
if p == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, seg := range p.Segments {
|
||||||
|
if seg.Ref.SessionKey == "" || seg.Ref.EndIdx < seg.Ref.StartIdx {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectionRequest struct {
|
||||||
|
AgentID string
|
||||||
|
SessionKey string
|
||||||
|
MaxTokens int
|
||||||
|
IncludeTools bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImmutableHistoryReader resolves lossless references to original messages.
|
||||||
|
type ImmutableHistoryReader interface {
|
||||||
|
ListRecallItems(ctx context.Context, agentID, sessionKey string, limit, offset int) ([]*RecallItem, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveContextBuilder materializes the turn-time projection from immutable
|
||||||
|
// storage, retrieval tiers, and DAG summaries.
|
||||||
|
type ActiveContextBuilder interface {
|
||||||
|
BuildActiveContext(ctx context.Context, req ProjectionRequest) (*ActiveContextProjection, error)
|
||||||
|
}
|
||||||
47
pkg/memory/kernel_contract_test.go
Normal file
47
pkg/memory/kernel_contract_test.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActiveContextProjection_TotalTokens(t *testing.T) {
|
||||||
|
p := &ActiveContextProjection{
|
||||||
|
Segments: []ProjectionSegment{
|
||||||
|
{Tokens: 120},
|
||||||
|
{Tokens: 80},
|
||||||
|
{Tokens: 35},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, 235, p.TotalTokens())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActiveContextProjection_HasLosslessRefs(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
p := &ActiveContextProjection{
|
||||||
|
Segments: []ProjectionSegment{
|
||||||
|
{
|
||||||
|
Kind: ProjectionSegmentRecent,
|
||||||
|
Source: "session-tail",
|
||||||
|
Text: "latest messages",
|
||||||
|
Tokens: 64,
|
||||||
|
Ref: ImmutableSpanRef{
|
||||||
|
SessionKey: "s1",
|
||||||
|
StartIdx: 10,
|
||||||
|
EndIdx: 15,
|
||||||
|
FirstID: ids.New(),
|
||||||
|
LastID: ids.New(),
|
||||||
|
FromTime: now,
|
||||||
|
ToTime: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.True(t, p.HasLosslessRefs())
|
||||||
|
|
||||||
|
p.Segments[0].Ref.SessionKey = ""
|
||||||
|
assert.False(t, p.HasLosslessRefs())
|
||||||
|
}
|
||||||
86
pkg/memory/migrations/011_dag_tables.go
Normal file
86
pkg/memory/migrations/011_dag_tables.go
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pressly/goose/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
goose.AddMigrationContext(up011DAGTables, down011DAGTables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func up011DAGTables(ctx context.Context, tx *sql.Tx) error {
|
||||||
|
stmts := []string{
|
||||||
|
`CREATE TABLE IF NOT EXISTS dag_snapshots (
|
||||||
|
id BLOB PRIMARY KEY,
|
||||||
|
agent_id TEXT NOT NULL,
|
||||||
|
session_key TEXT NOT NULL DEFAULT '',
|
||||||
|
from_msg_idx INTEGER NOT NULL DEFAULT 0,
|
||||||
|
to_msg_idx INTEGER NOT NULL DEFAULT 0,
|
||||||
|
msg_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
roots_json TEXT NOT NULL DEFAULT '[]',
|
||||||
|
content_hash TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_snapshots_agent_session ON dag_snapshots(agent_id, session_key)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_snapshots_created_at ON dag_snapshots(created_at DESC)`,
|
||||||
|
|
||||||
|
`CREATE TABLE IF NOT EXISTS dag_nodes (
|
||||||
|
id BLOB PRIMARY KEY,
|
||||||
|
snapshot_id BLOB NOT NULL REFERENCES dag_snapshots(id) ON DELETE CASCADE,
|
||||||
|
node_id TEXT NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 1,
|
||||||
|
summary TEXT NOT NULL DEFAULT '',
|
||||||
|
tokens INTEGER NOT NULL DEFAULT 0,
|
||||||
|
start_idx INTEGER NOT NULL DEFAULT 0,
|
||||||
|
end_idx INTEGER NOT NULL DEFAULT 0,
|
||||||
|
span INTEGER NOT NULL DEFAULT 0,
|
||||||
|
content_hash TEXT NOT NULL DEFAULT '',
|
||||||
|
metrics_json JSON NOT NULL DEFAULT '{}',
|
||||||
|
metadata_json JSON NOT NULL DEFAULT '{}',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_nodes_snapshot ON dag_nodes(snapshot_id)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_nodes_node_id ON dag_nodes(snapshot_id, node_id)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_nodes_level_start ON dag_nodes(snapshot_id, level, start_idx)`,
|
||||||
|
|
||||||
|
`CREATE TABLE IF NOT EXISTS dag_edges (
|
||||||
|
id BLOB PRIMARY KEY,
|
||||||
|
snapshot_id BLOB NOT NULL REFERENCES dag_snapshots(id) ON DELETE CASCADE,
|
||||||
|
parent_node_id BLOB NOT NULL REFERENCES dag_nodes(id) ON DELETE CASCADE,
|
||||||
|
child_node_id BLOB NOT NULL REFERENCES dag_nodes(id) ON DELETE CASCADE,
|
||||||
|
edge_index INTEGER NOT NULL DEFAULT 0,
|
||||||
|
metadata_json JSON NOT NULL DEFAULT '{}',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_edges_snapshot ON dag_edges(snapshot_id)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_edges_parent ON dag_edges(parent_node_id)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_dag_edges_child ON dag_edges(child_node_id)`,
|
||||||
|
}
|
||||||
|
for _, s := range stmts {
|
||||||
|
if _, err := tx.ExecContext(ctx, s); err != nil {
|
||||||
|
return fmt.Errorf("011_dag_tables up: %w\nSQL: %s", err, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func down011DAGTables(ctx context.Context, tx *sql.Tx) error {
|
||||||
|
stmts := []string{
|
||||||
|
`DROP TABLE IF EXISTS dag_edges`,
|
||||||
|
`DROP TABLE IF EXISTS dag_nodes`,
|
||||||
|
`DROP TABLE IF EXISTS dag_snapshots`,
|
||||||
|
}
|
||||||
|
for _, s := range stmts {
|
||||||
|
if _, err := tx.ExecContext(ctx, s); err != nil {
|
||||||
|
return fmt.Errorf("011_dag_tables down: %w\nSQL: %s", err, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
77
pkg/memory/migrations/012_map_operator_runs.go
Normal file
77
pkg/memory/migrations/012_map_operator_runs.go
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pressly/goose/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
goose.AddMigrationContext(up012MapOperatorRuns, down012MapOperatorRuns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func up012MapOperatorRuns(ctx context.Context, tx *sql.Tx) error {
|
||||||
|
stmts := []string{
|
||||||
|
`CREATE TABLE IF NOT EXISTS map_runs (
|
||||||
|
id BLOB PRIMARY KEY,
|
||||||
|
agent_id TEXT NOT NULL,
|
||||||
|
session_key TEXT NOT NULL,
|
||||||
|
operator_kind TEXT NOT NULL,
|
||||||
|
idempotency_key TEXT,
|
||||||
|
status TEXT NOT NULL DEFAULT 'queued',
|
||||||
|
total_items INTEGER NOT NULL DEFAULT 0,
|
||||||
|
queued_items INTEGER NOT NULL DEFAULT 0,
|
||||||
|
running_items INTEGER NOT NULL DEFAULT 0,
|
||||||
|
succeeded_items INTEGER NOT NULL DEFAULT 0,
|
||||||
|
failed_items INTEGER NOT NULL DEFAULT 0,
|
||||||
|
spec_fb BLOB NOT NULL,
|
||||||
|
last_error TEXT,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||||
|
completed_at DATETIME
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_map_runs_agent_session_created_at ON map_runs(agent_id, session_key, created_at DESC)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_map_runs_status_updated_at ON map_runs(status, updated_at DESC)`,
|
||||||
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_map_runs_dedupe ON map_runs(agent_id, session_key, operator_kind, idempotency_key) WHERE idempotency_key IS NOT NULL`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS map_items (
|
||||||
|
id BLOB PRIMARY KEY,
|
||||||
|
run_id BLOB NOT NULL REFERENCES map_runs(id) ON DELETE CASCADE,
|
||||||
|
item_index INTEGER NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'queued',
|
||||||
|
attempts INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_error TEXT,
|
||||||
|
input_fb BLOB NOT NULL,
|
||||||
|
output_fb BLOB,
|
||||||
|
input_hash TEXT,
|
||||||
|
output_hash TEXT,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||||
|
completed_at DATETIME,
|
||||||
|
UNIQUE(run_id, item_index)
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_map_items_run_id_status_item_index ON map_items(run_id, status, item_index)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_map_items_run_id_item_index ON map_items(run_id, item_index)`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range stmts {
|
||||||
|
if _, err := tx.ExecContext(ctx, s); err != nil {
|
||||||
|
return fmt.Errorf("012_map_operator_runs up: %w\nSQL: %s", err, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func down012MapOperatorRuns(ctx context.Context, tx *sql.Tx) error {
|
||||||
|
stmts := []string{
|
||||||
|
`DROP TABLE IF EXISTS map_items`,
|
||||||
|
`DROP TABLE IF EXISTS map_runs`,
|
||||||
|
}
|
||||||
|
for _, s := range stmts {
|
||||||
|
if _, err := tx.ExecContext(ctx, s); err != nil {
|
||||||
|
return fmt.Errorf("012_map_operator_runs down: %w\nSQL: %s", err, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
638
pkg/memory/sqlc/dag.sql.go
Normal file
638
pkg/memory/sqlc/dag.sql.go
Normal file
|
|
@ -0,0 +1,638 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: dag.sql
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
const GetDAGNodeBySnapshotAndNodeID = `-- name: GetDAGNodeBySnapshotAndNodeID :one
|
||||||
|
SELECT id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_nodes
|
||||||
|
WHERE snapshot_id = ?1
|
||||||
|
AND node_id = ?2
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDAGNodeBySnapshotAndNodeIDParams struct {
|
||||||
|
SnapshotID ids.UUID `db:"snapshot_id" json:"snapshot_id"`
|
||||||
|
NodeID string `db:"node_id" json:"node_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDAGNodeBySnapshotAndNodeID
|
||||||
|
//
|
||||||
|
// SELECT id,
|
||||||
|
// snapshot_id,
|
||||||
|
// node_id,
|
||||||
|
// level,
|
||||||
|
// summary,
|
||||||
|
// tokens,
|
||||||
|
// start_idx,
|
||||||
|
// end_idx,
|
||||||
|
// span,
|
||||||
|
// content_hash,
|
||||||
|
// metrics_json,
|
||||||
|
// metadata_json,
|
||||||
|
// created_at,
|
||||||
|
// updated_at
|
||||||
|
// FROM dag_nodes
|
||||||
|
// WHERE snapshot_id = ?1
|
||||||
|
// AND node_id = ?2
|
||||||
|
// LIMIT 1
|
||||||
|
func (q *Queries) GetDAGNodeBySnapshotAndNodeID(ctx context.Context, arg GetDAGNodeBySnapshotAndNodeIDParams) (DagNode, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, GetDAGNodeBySnapshotAndNodeID, arg.SnapshotID, arg.NodeID)
|
||||||
|
var i DagNode
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SnapshotID,
|
||||||
|
&i.NodeID,
|
||||||
|
&i.Level,
|
||||||
|
&i.Summary,
|
||||||
|
&i.Tokens,
|
||||||
|
&i.StartIdx,
|
||||||
|
&i.EndIdx,
|
||||||
|
&i.Span,
|
||||||
|
&i.ContentHash,
|
||||||
|
&i.MetricsJson,
|
||||||
|
&i.MetadataJson,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetDAGSnapshotByID = `-- name: GetDAGSnapshotByID :one
|
||||||
|
SELECT id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_snapshots
|
||||||
|
WHERE id = ?1
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDAGSnapshotByIDParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDAGSnapshotByID
|
||||||
|
//
|
||||||
|
// SELECT id,
|
||||||
|
// agent_id,
|
||||||
|
// session_key,
|
||||||
|
// from_msg_idx,
|
||||||
|
// to_msg_idx,
|
||||||
|
// msg_count,
|
||||||
|
// roots_json,
|
||||||
|
// content_hash,
|
||||||
|
// created_at,
|
||||||
|
// updated_at
|
||||||
|
// FROM dag_snapshots
|
||||||
|
// WHERE id = ?1
|
||||||
|
// LIMIT 1
|
||||||
|
func (q *Queries) GetDAGSnapshotByID(ctx context.Context, arg GetDAGSnapshotByIDParams) (DagSnapshot, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, GetDAGSnapshotByID, arg.ID)
|
||||||
|
var i DagSnapshot
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.FromMsgIdx,
|
||||||
|
&i.ToMsgIdx,
|
||||||
|
&i.MsgCount,
|
||||||
|
&i.RootsJson,
|
||||||
|
&i.ContentHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetLatestDAGSnapshotBySession = `-- name: GetLatestDAGSnapshotBySession :one
|
||||||
|
SELECT id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_snapshots
|
||||||
|
WHERE agent_id = ?1
|
||||||
|
AND session_key = ?2
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetLatestDAGSnapshotBySessionParams struct {
|
||||||
|
AgentID string `db:"agent_id" json:"agent_id"`
|
||||||
|
SessionKey string `db:"session_key" json:"session_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestDAGSnapshotBySession
|
||||||
|
//
|
||||||
|
// SELECT id,
|
||||||
|
// agent_id,
|
||||||
|
// session_key,
|
||||||
|
// from_msg_idx,
|
||||||
|
// to_msg_idx,
|
||||||
|
// msg_count,
|
||||||
|
// roots_json,
|
||||||
|
// content_hash,
|
||||||
|
// created_at,
|
||||||
|
// updated_at
|
||||||
|
// FROM dag_snapshots
|
||||||
|
// WHERE agent_id = ?1
|
||||||
|
// AND session_key = ?2
|
||||||
|
// ORDER BY created_at DESC
|
||||||
|
// LIMIT 1
|
||||||
|
func (q *Queries) GetLatestDAGSnapshotBySession(ctx context.Context, arg GetLatestDAGSnapshotBySessionParams) (DagSnapshot, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, GetLatestDAGSnapshotBySession, arg.AgentID, arg.SessionKey)
|
||||||
|
var i DagSnapshot
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.FromMsgIdx,
|
||||||
|
&i.ToMsgIdx,
|
||||||
|
&i.MsgCount,
|
||||||
|
&i.RootsJson,
|
||||||
|
&i.ContentHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertDAGEdge = `-- name: InsertDAGEdge :exec
|
||||||
|
INSERT INTO dag_edges (
|
||||||
|
id,
|
||||||
|
snapshot_id,
|
||||||
|
parent_node_id,
|
||||||
|
child_node_id,
|
||||||
|
edge_index,
|
||||||
|
metadata_json
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
?1,
|
||||||
|
?2,
|
||||||
|
?3,
|
||||||
|
?4,
|
||||||
|
?5,
|
||||||
|
?6
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertDAGEdgeParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
SnapshotID ids.UUID `db:"snapshot_id" json:"snapshot_id"`
|
||||||
|
ParentNodeID ids.UUID `db:"parent_node_id" json:"parent_node_id"`
|
||||||
|
ChildNodeID ids.UUID `db:"child_node_id" json:"child_node_id"`
|
||||||
|
EdgeIndex int64 `db:"edge_index" json:"edge_index"`
|
||||||
|
MetadataJson json.RawMessage `db:"metadata_json" json:"metadata_json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertDAGEdge
|
||||||
|
//
|
||||||
|
// INSERT INTO dag_edges (
|
||||||
|
// id,
|
||||||
|
// snapshot_id,
|
||||||
|
// parent_node_id,
|
||||||
|
// child_node_id,
|
||||||
|
// edge_index,
|
||||||
|
// metadata_json
|
||||||
|
// )
|
||||||
|
// VALUES (
|
||||||
|
// ?1,
|
||||||
|
// ?2,
|
||||||
|
// ?3,
|
||||||
|
// ?4,
|
||||||
|
// ?5,
|
||||||
|
// ?6
|
||||||
|
// )
|
||||||
|
func (q *Queries) InsertDAGEdge(ctx context.Context, arg InsertDAGEdgeParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, InsertDAGEdge,
|
||||||
|
arg.ID,
|
||||||
|
arg.SnapshotID,
|
||||||
|
arg.ParentNodeID,
|
||||||
|
arg.ChildNodeID,
|
||||||
|
arg.EdgeIndex,
|
||||||
|
arg.MetadataJson,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertDAGNode = `-- name: InsertDAGNode :one
|
||||||
|
INSERT INTO dag_nodes (
|
||||||
|
id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
?1,
|
||||||
|
?2,
|
||||||
|
?3,
|
||||||
|
?4,
|
||||||
|
?5,
|
||||||
|
?6,
|
||||||
|
?7,
|
||||||
|
?8,
|
||||||
|
?9,
|
||||||
|
?10,
|
||||||
|
?11,
|
||||||
|
?12
|
||||||
|
)
|
||||||
|
RETURNING id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertDAGNodeParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
SnapshotID ids.UUID `db:"snapshot_id" json:"snapshot_id"`
|
||||||
|
NodeID string `db:"node_id" json:"node_id"`
|
||||||
|
Level int64 `db:"level" json:"level"`
|
||||||
|
Summary string `db:"summary" json:"summary"`
|
||||||
|
Tokens int64 `db:"tokens" json:"tokens"`
|
||||||
|
StartIdx int64 `db:"start_idx" json:"start_idx"`
|
||||||
|
EndIdx int64 `db:"end_idx" json:"end_idx"`
|
||||||
|
Span int64 `db:"span" json:"span"`
|
||||||
|
ContentHash string `db:"content_hash" json:"content_hash"`
|
||||||
|
MetricsJson json.RawMessage `db:"metrics_json" json:"metrics_json"`
|
||||||
|
MetadataJson json.RawMessage `db:"metadata_json" json:"metadata_json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertDAGNode
|
||||||
|
//
|
||||||
|
// INSERT INTO dag_nodes (
|
||||||
|
// id,
|
||||||
|
// snapshot_id,
|
||||||
|
// node_id,
|
||||||
|
// level,
|
||||||
|
// summary,
|
||||||
|
// tokens,
|
||||||
|
// start_idx,
|
||||||
|
// end_idx,
|
||||||
|
// span,
|
||||||
|
// content_hash,
|
||||||
|
// metrics_json,
|
||||||
|
// metadata_json
|
||||||
|
// )
|
||||||
|
// VALUES (
|
||||||
|
// ?1,
|
||||||
|
// ?2,
|
||||||
|
// ?3,
|
||||||
|
// ?4,
|
||||||
|
// ?5,
|
||||||
|
// ?6,
|
||||||
|
// ?7,
|
||||||
|
// ?8,
|
||||||
|
// ?9,
|
||||||
|
// ?10,
|
||||||
|
// ?11,
|
||||||
|
// ?12
|
||||||
|
// )
|
||||||
|
// RETURNING id,
|
||||||
|
// snapshot_id,
|
||||||
|
// node_id,
|
||||||
|
// level,
|
||||||
|
// summary,
|
||||||
|
// tokens,
|
||||||
|
// start_idx,
|
||||||
|
// end_idx,
|
||||||
|
// span,
|
||||||
|
// content_hash,
|
||||||
|
// metrics_json,
|
||||||
|
// metadata_json,
|
||||||
|
// created_at,
|
||||||
|
// updated_at
|
||||||
|
func (q *Queries) InsertDAGNode(ctx context.Context, arg InsertDAGNodeParams) (DagNode, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, InsertDAGNode,
|
||||||
|
arg.ID,
|
||||||
|
arg.SnapshotID,
|
||||||
|
arg.NodeID,
|
||||||
|
arg.Level,
|
||||||
|
arg.Summary,
|
||||||
|
arg.Tokens,
|
||||||
|
arg.StartIdx,
|
||||||
|
arg.EndIdx,
|
||||||
|
arg.Span,
|
||||||
|
arg.ContentHash,
|
||||||
|
arg.MetricsJson,
|
||||||
|
arg.MetadataJson,
|
||||||
|
)
|
||||||
|
var i DagNode
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SnapshotID,
|
||||||
|
&i.NodeID,
|
||||||
|
&i.Level,
|
||||||
|
&i.Summary,
|
||||||
|
&i.Tokens,
|
||||||
|
&i.StartIdx,
|
||||||
|
&i.EndIdx,
|
||||||
|
&i.Span,
|
||||||
|
&i.ContentHash,
|
||||||
|
&i.MetricsJson,
|
||||||
|
&i.MetadataJson,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertDAGSnapshot = `-- name: InsertDAGSnapshot :one
|
||||||
|
INSERT INTO dag_snapshots (
|
||||||
|
id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
?1,
|
||||||
|
?2,
|
||||||
|
?3,
|
||||||
|
?4,
|
||||||
|
?5,
|
||||||
|
?6,
|
||||||
|
?7,
|
||||||
|
?8
|
||||||
|
)
|
||||||
|
RETURNING id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertDAGSnapshotParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
AgentID string `db:"agent_id" json:"agent_id"`
|
||||||
|
SessionKey string `db:"session_key" json:"session_key"`
|
||||||
|
FromMsgIdx int64 `db:"from_msg_idx" json:"from_msg_idx"`
|
||||||
|
ToMsgIdx int64 `db:"to_msg_idx" json:"to_msg_idx"`
|
||||||
|
MsgCount int64 `db:"msg_count" json:"msg_count"`
|
||||||
|
RootsJson string `db:"roots_json" json:"roots_json"`
|
||||||
|
ContentHash string `db:"content_hash" json:"content_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DAG persistence queries
|
||||||
|
//
|
||||||
|
// INSERT INTO dag_snapshots (
|
||||||
|
// id,
|
||||||
|
// agent_id,
|
||||||
|
// session_key,
|
||||||
|
// from_msg_idx,
|
||||||
|
// to_msg_idx,
|
||||||
|
// msg_count,
|
||||||
|
// roots_json,
|
||||||
|
// content_hash
|
||||||
|
// )
|
||||||
|
// VALUES (
|
||||||
|
// ?1,
|
||||||
|
// ?2,
|
||||||
|
// ?3,
|
||||||
|
// ?4,
|
||||||
|
// ?5,
|
||||||
|
// ?6,
|
||||||
|
// ?7,
|
||||||
|
// ?8
|
||||||
|
// )
|
||||||
|
// RETURNING id,
|
||||||
|
// agent_id,
|
||||||
|
// session_key,
|
||||||
|
// from_msg_idx,
|
||||||
|
// to_msg_idx,
|
||||||
|
// msg_count,
|
||||||
|
// roots_json,
|
||||||
|
// content_hash,
|
||||||
|
// created_at,
|
||||||
|
// updated_at
|
||||||
|
func (q *Queries) InsertDAGSnapshot(ctx context.Context, arg InsertDAGSnapshotParams) (DagSnapshot, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, InsertDAGSnapshot,
|
||||||
|
arg.ID,
|
||||||
|
arg.AgentID,
|
||||||
|
arg.SessionKey,
|
||||||
|
arg.FromMsgIdx,
|
||||||
|
arg.ToMsgIdx,
|
||||||
|
arg.MsgCount,
|
||||||
|
arg.RootsJson,
|
||||||
|
arg.ContentHash,
|
||||||
|
)
|
||||||
|
var i DagSnapshot
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.FromMsgIdx,
|
||||||
|
&i.ToMsgIdx,
|
||||||
|
&i.MsgCount,
|
||||||
|
&i.RootsJson,
|
||||||
|
&i.ContentHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListDAGEdgesBySnapshotID = `-- name: ListDAGEdgesBySnapshotID :many
|
||||||
|
SELECT id,
|
||||||
|
snapshot_id,
|
||||||
|
parent_node_id,
|
||||||
|
child_node_id,
|
||||||
|
edge_index,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_edges
|
||||||
|
WHERE snapshot_id = ?1
|
||||||
|
ORDER BY edge_index ASC,
|
||||||
|
created_at ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListDAGEdgesBySnapshotIDParams struct {
|
||||||
|
SnapshotID ids.UUID `db:"snapshot_id" json:"snapshot_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDAGEdgesBySnapshotID
|
||||||
|
//
|
||||||
|
// SELECT id,
|
||||||
|
// snapshot_id,
|
||||||
|
// parent_node_id,
|
||||||
|
// child_node_id,
|
||||||
|
// edge_index,
|
||||||
|
// metadata_json,
|
||||||
|
// created_at,
|
||||||
|
// updated_at
|
||||||
|
// FROM dag_edges
|
||||||
|
// WHERE snapshot_id = ?1
|
||||||
|
// ORDER BY edge_index ASC,
|
||||||
|
// created_at ASC
|
||||||
|
func (q *Queries) ListDAGEdgesBySnapshotID(ctx context.Context, arg ListDAGEdgesBySnapshotIDParams) ([]DagEdge, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, ListDAGEdgesBySnapshotID, arg.SnapshotID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []DagEdge{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i DagEdge
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SnapshotID,
|
||||||
|
&i.ParentNodeID,
|
||||||
|
&i.ChildNodeID,
|
||||||
|
&i.EdgeIndex,
|
||||||
|
&i.MetadataJson,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListDAGNodesBySnapshotID = `-- name: ListDAGNodesBySnapshotID :many
|
||||||
|
SELECT id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_nodes
|
||||||
|
WHERE snapshot_id = ?1
|
||||||
|
ORDER BY start_idx ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListDAGNodesBySnapshotIDParams struct {
|
||||||
|
SnapshotID ids.UUID `db:"snapshot_id" json:"snapshot_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDAGNodesBySnapshotID
|
||||||
|
//
|
||||||
|
// SELECT id,
|
||||||
|
// snapshot_id,
|
||||||
|
// node_id,
|
||||||
|
// level,
|
||||||
|
// summary,
|
||||||
|
// tokens,
|
||||||
|
// start_idx,
|
||||||
|
// end_idx,
|
||||||
|
// span,
|
||||||
|
// content_hash,
|
||||||
|
// metrics_json,
|
||||||
|
// metadata_json,
|
||||||
|
// created_at,
|
||||||
|
// updated_at
|
||||||
|
// FROM dag_nodes
|
||||||
|
// WHERE snapshot_id = ?1
|
||||||
|
// ORDER BY start_idx ASC
|
||||||
|
func (q *Queries) ListDAGNodesBySnapshotID(ctx context.Context, arg ListDAGNodesBySnapshotIDParams) ([]DagNode, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, ListDAGNodesBySnapshotID, arg.SnapshotID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []DagNode{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i DagNode
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SnapshotID,
|
||||||
|
&i.NodeID,
|
||||||
|
&i.Level,
|
||||||
|
&i.Summary,
|
||||||
|
&i.Tokens,
|
||||||
|
&i.StartIdx,
|
||||||
|
&i.EndIdx,
|
||||||
|
&i.Span,
|
||||||
|
&i.ContentHash,
|
||||||
|
&i.MetricsJson,
|
||||||
|
&i.MetadataJson,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
815
pkg/memory/sqlc/map_ops.sql.go
Normal file
815
pkg/memory/sqlc/map_ops.sql.go
Normal file
|
|
@ -0,0 +1,815 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: map_ops.sql
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CountMapItemsByRun = `-- name: CountMapItemsByRun :one
|
||||||
|
SELECT count(*) AS count
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = ?1
|
||||||
|
`
|
||||||
|
|
||||||
|
type CountMapItemsByRunParams struct {
|
||||||
|
RunID ids.UUID `db:"run_id" json:"run_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountMapItemsByRun
|
||||||
|
//
|
||||||
|
// SELECT count(*) AS count
|
||||||
|
// FROM map_items
|
||||||
|
// WHERE run_id = ?1
|
||||||
|
func (q *Queries) CountMapItemsByRun(ctx context.Context, arg CountMapItemsByRunParams) (int64, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, CountMapItemsByRun, arg.RunID)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const CountMapItemsByRunAndStatus = `-- name: CountMapItemsByRunAndStatus :many
|
||||||
|
SELECT status,
|
||||||
|
count(*) AS count
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = ?1
|
||||||
|
GROUP BY status
|
||||||
|
`
|
||||||
|
|
||||||
|
type CountMapItemsByRunAndStatusParams struct {
|
||||||
|
RunID ids.UUID `db:"run_id" json:"run_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CountMapItemsByRunAndStatusRow struct {
|
||||||
|
Status string `db:"status" json:"status"`
|
||||||
|
Count int64 `db:"count" json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountMapItemsByRunAndStatus
|
||||||
|
//
|
||||||
|
// SELECT status,
|
||||||
|
// count(*) AS count
|
||||||
|
// FROM map_items
|
||||||
|
// WHERE run_id = ?1
|
||||||
|
// GROUP BY status
|
||||||
|
func (q *Queries) CountMapItemsByRunAndStatus(ctx context.Context, arg CountMapItemsByRunAndStatusParams) ([]CountMapItemsByRunAndStatusRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, CountMapItemsByRunAndStatus, arg.RunID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []CountMapItemsByRunAndStatusRow{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i CountMapItemsByRunAndStatusRow
|
||||||
|
if err := rows.Scan(&i.Status, &i.Count); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetMapItemByID = `-- name: GetMapItemByID :one
|
||||||
|
SELECT id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
FROM map_items
|
||||||
|
WHERE id = ?1
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetMapItemByIDParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMapItemByID
|
||||||
|
//
|
||||||
|
// SELECT id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
// FROM map_items
|
||||||
|
// WHERE id = ?1
|
||||||
|
// LIMIT 1
|
||||||
|
func (q *Queries) GetMapItemByID(ctx context.Context, arg GetMapItemByIDParams) (MapItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, GetMapItemByID, arg.ID)
|
||||||
|
var i MapItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RunID,
|
||||||
|
&i.ItemIndex,
|
||||||
|
&i.Status,
|
||||||
|
&i.Attempts,
|
||||||
|
&i.LastError,
|
||||||
|
&i.InputFb,
|
||||||
|
&i.OutputFb,
|
||||||
|
&i.InputHash,
|
||||||
|
&i.OutputHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetMapItemByRunAndIndex = `-- name: GetMapItemByRunAndIndex :one
|
||||||
|
SELECT id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = ?1
|
||||||
|
AND item_index = ?2
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetMapItemByRunAndIndexParams struct {
|
||||||
|
RunID ids.UUID `db:"run_id" json:"run_id"`
|
||||||
|
ItemIndex int64 `db:"item_index" json:"item_index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMapItemByRunAndIndex
|
||||||
|
//
|
||||||
|
// SELECT id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
// FROM map_items
|
||||||
|
// WHERE run_id = ?1
|
||||||
|
// AND item_index = ?2
|
||||||
|
// LIMIT 1
|
||||||
|
func (q *Queries) GetMapItemByRunAndIndex(ctx context.Context, arg GetMapItemByRunAndIndexParams) (MapItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, GetMapItemByRunAndIndex, arg.RunID, arg.ItemIndex)
|
||||||
|
var i MapItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RunID,
|
||||||
|
&i.ItemIndex,
|
||||||
|
&i.Status,
|
||||||
|
&i.Attempts,
|
||||||
|
&i.LastError,
|
||||||
|
&i.InputFb,
|
||||||
|
&i.OutputFb,
|
||||||
|
&i.InputHash,
|
||||||
|
&i.OutputHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetMapRunByID = `-- name: GetMapRunByID :one
|
||||||
|
SELECT id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
FROM map_runs
|
||||||
|
WHERE id = ?1
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetMapRunByIDParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMapRunByID
|
||||||
|
//
|
||||||
|
// SELECT id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
// FROM map_runs
|
||||||
|
// WHERE id = ?1
|
||||||
|
// LIMIT 1
|
||||||
|
func (q *Queries) GetMapRunByID(ctx context.Context, arg GetMapRunByIDParams) (MapRun, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, GetMapRunByID, arg.ID)
|
||||||
|
var i MapRun
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.OperatorKind,
|
||||||
|
&i.IdempotencyKey,
|
||||||
|
&i.Status,
|
||||||
|
&i.TotalItems,
|
||||||
|
&i.QueuedItems,
|
||||||
|
&i.RunningItems,
|
||||||
|
&i.SucceededItems,
|
||||||
|
&i.FailedItems,
|
||||||
|
&i.SpecFb,
|
||||||
|
&i.LastError,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetMapRunByIdempotencyKey = `-- name: GetMapRunByIdempotencyKey :one
|
||||||
|
SELECT id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
FROM map_runs
|
||||||
|
WHERE agent_id = ?1
|
||||||
|
AND session_key = ?2
|
||||||
|
AND operator_kind = ?3
|
||||||
|
AND idempotency_key = ?4
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetMapRunByIdempotencyKeyParams struct {
|
||||||
|
AgentID string `db:"agent_id" json:"agent_id"`
|
||||||
|
SessionKey string `db:"session_key" json:"session_key"`
|
||||||
|
OperatorKind string `db:"operator_kind" json:"operator_kind"`
|
||||||
|
IdempotencyKey *string `db:"idempotency_key" json:"idempotency_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMapRunByIdempotencyKey
|
||||||
|
//
|
||||||
|
// SELECT id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
// FROM map_runs
|
||||||
|
// WHERE agent_id = ?1
|
||||||
|
// AND session_key = ?2
|
||||||
|
// AND operator_kind = ?3
|
||||||
|
// AND idempotency_key = ?4
|
||||||
|
// LIMIT 1
|
||||||
|
func (q *Queries) GetMapRunByIdempotencyKey(ctx context.Context, arg GetMapRunByIdempotencyKeyParams) (MapRun, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, GetMapRunByIdempotencyKey,
|
||||||
|
arg.AgentID,
|
||||||
|
arg.SessionKey,
|
||||||
|
arg.OperatorKind,
|
||||||
|
arg.IdempotencyKey,
|
||||||
|
)
|
||||||
|
var i MapRun
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.OperatorKind,
|
||||||
|
&i.IdempotencyKey,
|
||||||
|
&i.Status,
|
||||||
|
&i.TotalItems,
|
||||||
|
&i.QueuedItems,
|
||||||
|
&i.RunningItems,
|
||||||
|
&i.SucceededItems,
|
||||||
|
&i.FailedItems,
|
||||||
|
&i.SpecFb,
|
||||||
|
&i.LastError,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertMapItem = `-- name: InsertMapItem :one
|
||||||
|
INSERT INTO map_items (
|
||||||
|
id,
|
||||||
|
run_id,
|
||||||
|
item_index,
|
||||||
|
status,
|
||||||
|
attempts,
|
||||||
|
last_error,
|
||||||
|
input_fb,
|
||||||
|
output_fb,
|
||||||
|
input_hash,
|
||||||
|
output_hash
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
?1,
|
||||||
|
?2,
|
||||||
|
?3,
|
||||||
|
?4,
|
||||||
|
?5,
|
||||||
|
?6,
|
||||||
|
?7,
|
||||||
|
?8,
|
||||||
|
?9,
|
||||||
|
?10
|
||||||
|
)
|
||||||
|
RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertMapItemParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
RunID ids.UUID `db:"run_id" json:"run_id"`
|
||||||
|
ItemIndex int64 `db:"item_index" json:"item_index"`
|
||||||
|
Status string `db:"status" json:"status"`
|
||||||
|
Attempts int64 `db:"attempts" json:"attempts"`
|
||||||
|
LastError *string `db:"last_error" json:"last_error"`
|
||||||
|
InputFb []byte `db:"input_fb" json:"input_fb"`
|
||||||
|
OutputFb []byte `db:"output_fb" json:"output_fb"`
|
||||||
|
InputHash *string `db:"input_hash" json:"input_hash"`
|
||||||
|
OutputHash *string `db:"output_hash" json:"output_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertMapItem
|
||||||
|
//
|
||||||
|
// INSERT INTO map_items (
|
||||||
|
// id,
|
||||||
|
// run_id,
|
||||||
|
// item_index,
|
||||||
|
// status,
|
||||||
|
// attempts,
|
||||||
|
// last_error,
|
||||||
|
// input_fb,
|
||||||
|
// output_fb,
|
||||||
|
// input_hash,
|
||||||
|
// output_hash
|
||||||
|
// )
|
||||||
|
// VALUES (
|
||||||
|
// ?1,
|
||||||
|
// ?2,
|
||||||
|
// ?3,
|
||||||
|
// ?4,
|
||||||
|
// ?5,
|
||||||
|
// ?6,
|
||||||
|
// ?7,
|
||||||
|
// ?8,
|
||||||
|
// ?9,
|
||||||
|
// ?10
|
||||||
|
// )
|
||||||
|
// RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
func (q *Queries) InsertMapItem(ctx context.Context, arg InsertMapItemParams) (MapItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, InsertMapItem,
|
||||||
|
arg.ID,
|
||||||
|
arg.RunID,
|
||||||
|
arg.ItemIndex,
|
||||||
|
arg.Status,
|
||||||
|
arg.Attempts,
|
||||||
|
arg.LastError,
|
||||||
|
arg.InputFb,
|
||||||
|
arg.OutputFb,
|
||||||
|
arg.InputHash,
|
||||||
|
arg.OutputHash,
|
||||||
|
)
|
||||||
|
var i MapItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RunID,
|
||||||
|
&i.ItemIndex,
|
||||||
|
&i.Status,
|
||||||
|
&i.Attempts,
|
||||||
|
&i.LastError,
|
||||||
|
&i.InputFb,
|
||||||
|
&i.OutputFb,
|
||||||
|
&i.InputHash,
|
||||||
|
&i.OutputHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertMapRun = `-- name: InsertMapRun :one
|
||||||
|
INSERT INTO map_runs (
|
||||||
|
id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
operator_kind,
|
||||||
|
idempotency_key,
|
||||||
|
status,
|
||||||
|
total_items,
|
||||||
|
queued_items,
|
||||||
|
running_items,
|
||||||
|
succeeded_items,
|
||||||
|
failed_items,
|
||||||
|
spec_fb,
|
||||||
|
last_error
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
?1,
|
||||||
|
?2,
|
||||||
|
?3,
|
||||||
|
?4,
|
||||||
|
?5,
|
||||||
|
?6,
|
||||||
|
?7,
|
||||||
|
?8,
|
||||||
|
?9,
|
||||||
|
?10,
|
||||||
|
?11,
|
||||||
|
?12,
|
||||||
|
?13
|
||||||
|
)
|
||||||
|
RETURNING id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertMapRunParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
AgentID string `db:"agent_id" json:"agent_id"`
|
||||||
|
SessionKey string `db:"session_key" json:"session_key"`
|
||||||
|
OperatorKind string `db:"operator_kind" json:"operator_kind"`
|
||||||
|
IdempotencyKey *string `db:"idempotency_key" json:"idempotency_key"`
|
||||||
|
Status string `db:"status" json:"status"`
|
||||||
|
TotalItems int64 `db:"total_items" json:"total_items"`
|
||||||
|
QueuedItems int64 `db:"queued_items" json:"queued_items"`
|
||||||
|
RunningItems int64 `db:"running_items" json:"running_items"`
|
||||||
|
SucceededItems int64 `db:"succeeded_items" json:"succeeded_items"`
|
||||||
|
FailedItems int64 `db:"failed_items" json:"failed_items"`
|
||||||
|
SpecFb []byte `db:"spec_fb" json:"spec_fb"`
|
||||||
|
LastError *string `db:"last_error" json:"last_error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertMapRun
|
||||||
|
//
|
||||||
|
// INSERT INTO map_runs (
|
||||||
|
// id,
|
||||||
|
// agent_id,
|
||||||
|
// session_key,
|
||||||
|
// operator_kind,
|
||||||
|
// idempotency_key,
|
||||||
|
// status,
|
||||||
|
// total_items,
|
||||||
|
// queued_items,
|
||||||
|
// running_items,
|
||||||
|
// succeeded_items,
|
||||||
|
// failed_items,
|
||||||
|
// spec_fb,
|
||||||
|
// last_error
|
||||||
|
// )
|
||||||
|
// VALUES (
|
||||||
|
// ?1,
|
||||||
|
// ?2,
|
||||||
|
// ?3,
|
||||||
|
// ?4,
|
||||||
|
// ?5,
|
||||||
|
// ?6,
|
||||||
|
// ?7,
|
||||||
|
// ?8,
|
||||||
|
// ?9,
|
||||||
|
// ?10,
|
||||||
|
// ?11,
|
||||||
|
// ?12,
|
||||||
|
// ?13
|
||||||
|
// )
|
||||||
|
// RETURNING id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
func (q *Queries) InsertMapRun(ctx context.Context, arg InsertMapRunParams) (MapRun, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, InsertMapRun,
|
||||||
|
arg.ID,
|
||||||
|
arg.AgentID,
|
||||||
|
arg.SessionKey,
|
||||||
|
arg.OperatorKind,
|
||||||
|
arg.IdempotencyKey,
|
||||||
|
arg.Status,
|
||||||
|
arg.TotalItems,
|
||||||
|
arg.QueuedItems,
|
||||||
|
arg.RunningItems,
|
||||||
|
arg.SucceededItems,
|
||||||
|
arg.FailedItems,
|
||||||
|
arg.SpecFb,
|
||||||
|
arg.LastError,
|
||||||
|
)
|
||||||
|
var i MapRun
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.OperatorKind,
|
||||||
|
&i.IdempotencyKey,
|
||||||
|
&i.Status,
|
||||||
|
&i.TotalItems,
|
||||||
|
&i.QueuedItems,
|
||||||
|
&i.RunningItems,
|
||||||
|
&i.SucceededItems,
|
||||||
|
&i.FailedItems,
|
||||||
|
&i.SpecFb,
|
||||||
|
&i.LastError,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListMapItemsByRunPaged = `-- name: ListMapItemsByRunPaged :many
|
||||||
|
SELECT id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = ?1
|
||||||
|
ORDER BY item_index ASC
|
||||||
|
LIMIT ?3 OFFSET ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListMapItemsByRunPagedParams struct {
|
||||||
|
RunID ids.UUID `db:"run_id" json:"run_id"`
|
||||||
|
Off int64 `db:"off" json:"off"`
|
||||||
|
Lim int64 `db:"lim" json:"lim"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMapItemsByRunPaged
|
||||||
|
//
|
||||||
|
// SELECT id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
// FROM map_items
|
||||||
|
// WHERE run_id = ?1
|
||||||
|
// ORDER BY item_index ASC
|
||||||
|
// LIMIT ?3 OFFSET ?2
|
||||||
|
func (q *Queries) ListMapItemsByRunPaged(ctx context.Context, arg ListMapItemsByRunPagedParams) ([]MapItem, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, ListMapItemsByRunPaged, arg.RunID, arg.Off, arg.Lim)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []MapItem{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i MapItem
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RunID,
|
||||||
|
&i.ItemIndex,
|
||||||
|
&i.Status,
|
||||||
|
&i.Attempts,
|
||||||
|
&i.LastError,
|
||||||
|
&i.InputFb,
|
||||||
|
&i.OutputFb,
|
||||||
|
&i.InputHash,
|
||||||
|
&i.OutputHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListMapRunsBySession = `-- name: ListMapRunsBySession :many
|
||||||
|
SELECT id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
FROM map_runs
|
||||||
|
WHERE agent_id = ?1
|
||||||
|
AND session_key = ?2
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT ?4 OFFSET ?3
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListMapRunsBySessionParams struct {
|
||||||
|
AgentID string `db:"agent_id" json:"agent_id"`
|
||||||
|
SessionKey string `db:"session_key" json:"session_key"`
|
||||||
|
Off int64 `db:"off" json:"off"`
|
||||||
|
Lim int64 `db:"lim" json:"lim"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMapRunsBySession
|
||||||
|
//
|
||||||
|
// SELECT id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
// FROM map_runs
|
||||||
|
// WHERE agent_id = ?1
|
||||||
|
// AND session_key = ?2
|
||||||
|
// ORDER BY created_at DESC
|
||||||
|
// LIMIT ?4 OFFSET ?3
|
||||||
|
func (q *Queries) ListMapRunsBySession(ctx context.Context, arg ListMapRunsBySessionParams) ([]MapRun, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, ListMapRunsBySession,
|
||||||
|
arg.AgentID,
|
||||||
|
arg.SessionKey,
|
||||||
|
arg.Off,
|
||||||
|
arg.Lim,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []MapRun{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i MapRun
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.OperatorKind,
|
||||||
|
&i.IdempotencyKey,
|
||||||
|
&i.Status,
|
||||||
|
&i.TotalItems,
|
||||||
|
&i.QueuedItems,
|
||||||
|
&i.RunningItems,
|
||||||
|
&i.SucceededItems,
|
||||||
|
&i.FailedItems,
|
||||||
|
&i.SpecFb,
|
||||||
|
&i.LastError,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarkMapItemFailed = `-- name: MarkMapItemFailed :one
|
||||||
|
UPDATE map_items
|
||||||
|
SET status = 'failed',
|
||||||
|
last_error = ?1,
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
WHERE id = ?2
|
||||||
|
RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type MarkMapItemFailedParams struct {
|
||||||
|
LastError *string `db:"last_error" json:"last_error"`
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkMapItemFailed
|
||||||
|
//
|
||||||
|
// UPDATE map_items
|
||||||
|
// SET status = 'failed',
|
||||||
|
// last_error = ?1,
|
||||||
|
// updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
// completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
// WHERE id = ?2
|
||||||
|
// RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
func (q *Queries) MarkMapItemFailed(ctx context.Context, arg MarkMapItemFailedParams) (MapItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, MarkMapItemFailed, arg.LastError, arg.ID)
|
||||||
|
var i MapItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RunID,
|
||||||
|
&i.ItemIndex,
|
||||||
|
&i.Status,
|
||||||
|
&i.Attempts,
|
||||||
|
&i.LastError,
|
||||||
|
&i.InputFb,
|
||||||
|
&i.OutputFb,
|
||||||
|
&i.InputHash,
|
||||||
|
&i.OutputHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarkMapItemRunning = `-- name: MarkMapItemRunning :one
|
||||||
|
UPDATE map_items
|
||||||
|
SET status = 'running',
|
||||||
|
attempts = attempts + 1,
|
||||||
|
last_error = NULL,
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
WHERE id = ?1
|
||||||
|
RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type MarkMapItemRunningParams struct {
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkMapItemRunning
|
||||||
|
//
|
||||||
|
// UPDATE map_items
|
||||||
|
// SET status = 'running',
|
||||||
|
// attempts = attempts + 1,
|
||||||
|
// last_error = NULL,
|
||||||
|
// updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
// WHERE id = ?1
|
||||||
|
// RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
func (q *Queries) MarkMapItemRunning(ctx context.Context, arg MarkMapItemRunningParams) (MapItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, MarkMapItemRunning, arg.ID)
|
||||||
|
var i MapItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RunID,
|
||||||
|
&i.ItemIndex,
|
||||||
|
&i.Status,
|
||||||
|
&i.Attempts,
|
||||||
|
&i.LastError,
|
||||||
|
&i.InputFb,
|
||||||
|
&i.OutputFb,
|
||||||
|
&i.InputHash,
|
||||||
|
&i.OutputHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarkMapItemSucceeded = `-- name: MarkMapItemSucceeded :one
|
||||||
|
UPDATE map_items
|
||||||
|
SET status = 'succeeded',
|
||||||
|
output_fb = ?1,
|
||||||
|
output_hash = ?2,
|
||||||
|
last_error = NULL,
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
WHERE id = ?3
|
||||||
|
RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type MarkMapItemSucceededParams struct {
|
||||||
|
OutputFb []byte `db:"output_fb" json:"output_fb"`
|
||||||
|
OutputHash *string `db:"output_hash" json:"output_hash"`
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkMapItemSucceeded
|
||||||
|
//
|
||||||
|
// UPDATE map_items
|
||||||
|
// SET status = 'succeeded',
|
||||||
|
// output_fb = ?1,
|
||||||
|
// output_hash = ?2,
|
||||||
|
// last_error = NULL,
|
||||||
|
// updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
// completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
// WHERE id = ?3
|
||||||
|
// RETURNING id, run_id, item_index, status, attempts, last_error, input_fb, output_fb, input_hash, output_hash, created_at, updated_at, completed_at
|
||||||
|
func (q *Queries) MarkMapItemSucceeded(ctx context.Context, arg MarkMapItemSucceededParams) (MapItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, MarkMapItemSucceeded, arg.OutputFb, arg.OutputHash, arg.ID)
|
||||||
|
var i MapItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RunID,
|
||||||
|
&i.ItemIndex,
|
||||||
|
&i.Status,
|
||||||
|
&i.Attempts,
|
||||||
|
&i.LastError,
|
||||||
|
&i.InputFb,
|
||||||
|
&i.OutputFb,
|
||||||
|
&i.InputHash,
|
||||||
|
&i.OutputHash,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateMapRunProgress = `-- name: UpdateMapRunProgress :one
|
||||||
|
UPDATE map_runs
|
||||||
|
SET status = ?1,
|
||||||
|
queued_items = ?2,
|
||||||
|
running_items = ?3,
|
||||||
|
succeeded_items = ?4,
|
||||||
|
failed_items = ?5,
|
||||||
|
last_error = ?6,
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
completed_at = ?7
|
||||||
|
WHERE id = ?8
|
||||||
|
RETURNING id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateMapRunProgressParams struct {
|
||||||
|
Status string `db:"status" json:"status"`
|
||||||
|
QueuedItems int64 `db:"queued_items" json:"queued_items"`
|
||||||
|
RunningItems int64 `db:"running_items" json:"running_items"`
|
||||||
|
SucceededItems int64 `db:"succeeded_items" json:"succeeded_items"`
|
||||||
|
FailedItems int64 `db:"failed_items" json:"failed_items"`
|
||||||
|
LastError *string `db:"last_error" json:"last_error"`
|
||||||
|
CompletedAt *time.Time `db:"completed_at" json:"completed_at"`
|
||||||
|
ID ids.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMapRunProgress
|
||||||
|
//
|
||||||
|
// UPDATE map_runs
|
||||||
|
// SET status = ?1,
|
||||||
|
// queued_items = ?2,
|
||||||
|
// running_items = ?3,
|
||||||
|
// succeeded_items = ?4,
|
||||||
|
// failed_items = ?5,
|
||||||
|
// last_error = ?6,
|
||||||
|
// updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
// completed_at = ?7
|
||||||
|
// WHERE id = ?8
|
||||||
|
// RETURNING id, agent_id, session_key, operator_kind, idempotency_key, status, total_items, queued_items, running_items, succeeded_items, failed_items, spec_fb, last_error, created_at, updated_at, completed_at
|
||||||
|
func (q *Queries) UpdateMapRunProgress(ctx context.Context, arg UpdateMapRunProgressParams) (MapRun, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, UpdateMapRunProgress,
|
||||||
|
arg.Status,
|
||||||
|
arg.QueuedItems,
|
||||||
|
arg.RunningItems,
|
||||||
|
arg.SucceededItems,
|
||||||
|
arg.FailedItems,
|
||||||
|
arg.LastError,
|
||||||
|
arg.CompletedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
var i MapRun
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AgentID,
|
||||||
|
&i.SessionKey,
|
||||||
|
&i.OperatorKind,
|
||||||
|
&i.IdempotencyKey,
|
||||||
|
&i.Status,
|
||||||
|
&i.TotalItems,
|
||||||
|
&i.QueuedItems,
|
||||||
|
&i.RunningItems,
|
||||||
|
&i.SucceededItems,
|
||||||
|
&i.FailedItems,
|
||||||
|
&i.SpecFb,
|
||||||
|
&i.LastError,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
172
pkg/memory/sqlc/queries/dag.sql
Normal file
172
pkg/memory/sqlc/queries/dag.sql
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
-- DAG persistence queries
|
||||||
|
-- name: InsertDAGSnapshot :one
|
||||||
|
INSERT INTO dag_snapshots (
|
||||||
|
id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(id),
|
||||||
|
sqlc.arg(agent_id),
|
||||||
|
sqlc.arg(session_key),
|
||||||
|
sqlc.arg(from_msg_idx),
|
||||||
|
sqlc.arg(to_msg_idx),
|
||||||
|
sqlc.arg(msg_count),
|
||||||
|
sqlc.arg(roots_json),
|
||||||
|
sqlc.arg(content_hash)
|
||||||
|
)
|
||||||
|
RETURNING id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash,
|
||||||
|
created_at,
|
||||||
|
updated_at;
|
||||||
|
-- name: InsertDAGNode :one
|
||||||
|
INSERT INTO dag_nodes (
|
||||||
|
id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(id),
|
||||||
|
sqlc.arg(snapshot_id),
|
||||||
|
sqlc.arg(node_id),
|
||||||
|
sqlc.arg(level),
|
||||||
|
sqlc.arg(summary),
|
||||||
|
sqlc.arg(tokens),
|
||||||
|
sqlc.arg(start_idx),
|
||||||
|
sqlc.arg(end_idx),
|
||||||
|
sqlc.arg(span),
|
||||||
|
sqlc.arg(content_hash),
|
||||||
|
sqlc.arg(metrics_json),
|
||||||
|
sqlc.arg(metadata_json)
|
||||||
|
)
|
||||||
|
RETURNING id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at;
|
||||||
|
-- name: InsertDAGEdge :exec
|
||||||
|
INSERT INTO dag_edges (
|
||||||
|
id,
|
||||||
|
snapshot_id,
|
||||||
|
parent_node_id,
|
||||||
|
child_node_id,
|
||||||
|
edge_index,
|
||||||
|
metadata_json
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(id),
|
||||||
|
sqlc.arg(snapshot_id),
|
||||||
|
sqlc.arg(parent_node_id),
|
||||||
|
sqlc.arg(child_node_id),
|
||||||
|
sqlc.arg(edge_index),
|
||||||
|
sqlc.arg(metadata_json)
|
||||||
|
);
|
||||||
|
-- name: GetLatestDAGSnapshotBySession :one
|
||||||
|
SELECT id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_snapshots
|
||||||
|
WHERE agent_id = sqlc.arg(agent_id)
|
||||||
|
AND session_key = sqlc.arg(session_key)
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: GetDAGSnapshotByID :one
|
||||||
|
SELECT id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
from_msg_idx,
|
||||||
|
to_msg_idx,
|
||||||
|
msg_count,
|
||||||
|
roots_json,
|
||||||
|
content_hash,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_snapshots
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: ListDAGNodesBySnapshotID :many
|
||||||
|
SELECT id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_nodes
|
||||||
|
WHERE snapshot_id = sqlc.arg(snapshot_id)
|
||||||
|
ORDER BY start_idx ASC;
|
||||||
|
-- name: GetDAGNodeBySnapshotAndNodeID :one
|
||||||
|
SELECT id,
|
||||||
|
snapshot_id,
|
||||||
|
node_id,
|
||||||
|
level,
|
||||||
|
summary,
|
||||||
|
tokens,
|
||||||
|
start_idx,
|
||||||
|
end_idx,
|
||||||
|
span,
|
||||||
|
content_hash,
|
||||||
|
metrics_json,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_nodes
|
||||||
|
WHERE snapshot_id = sqlc.arg(snapshot_id)
|
||||||
|
AND node_id = sqlc.arg(node_id)
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: ListDAGEdgesBySnapshotID :many
|
||||||
|
SELECT id,
|
||||||
|
snapshot_id,
|
||||||
|
parent_node_id,
|
||||||
|
child_node_id,
|
||||||
|
edge_index,
|
||||||
|
metadata_json,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM dag_edges
|
||||||
|
WHERE snapshot_id = sqlc.arg(snapshot_id)
|
||||||
|
ORDER BY edge_index ASC,
|
||||||
|
created_at ASC;
|
||||||
143
pkg/memory/sqlc/queries/map_ops.sql
Normal file
143
pkg/memory/sqlc/queries/map_ops.sql
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
-- name: InsertMapRun :one
|
||||||
|
INSERT INTO map_runs (
|
||||||
|
id,
|
||||||
|
agent_id,
|
||||||
|
session_key,
|
||||||
|
operator_kind,
|
||||||
|
idempotency_key,
|
||||||
|
status,
|
||||||
|
total_items,
|
||||||
|
queued_items,
|
||||||
|
running_items,
|
||||||
|
succeeded_items,
|
||||||
|
failed_items,
|
||||||
|
spec_fb,
|
||||||
|
last_error
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(id),
|
||||||
|
sqlc.arg(agent_id),
|
||||||
|
sqlc.arg(session_key),
|
||||||
|
sqlc.arg(operator_kind),
|
||||||
|
sqlc.arg(idempotency_key),
|
||||||
|
sqlc.arg(status),
|
||||||
|
sqlc.arg(total_items),
|
||||||
|
sqlc.arg(queued_items),
|
||||||
|
sqlc.arg(running_items),
|
||||||
|
sqlc.arg(succeeded_items),
|
||||||
|
sqlc.arg(failed_items),
|
||||||
|
sqlc.arg(spec_fb),
|
||||||
|
sqlc.arg(last_error)
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
-- name: GetMapRunByIdempotencyKey :one
|
||||||
|
SELECT *
|
||||||
|
FROM map_runs
|
||||||
|
WHERE agent_id = sqlc.arg(agent_id)
|
||||||
|
AND session_key = sqlc.arg(session_key)
|
||||||
|
AND operator_kind = sqlc.arg(operator_kind)
|
||||||
|
AND idempotency_key = sqlc.arg(idempotency_key)
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: GetMapRunByID :one
|
||||||
|
SELECT *
|
||||||
|
FROM map_runs
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: ListMapRunsBySession :many
|
||||||
|
SELECT *
|
||||||
|
FROM map_runs
|
||||||
|
WHERE agent_id = sqlc.arg(agent_id)
|
||||||
|
AND session_key = sqlc.arg(session_key)
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT sqlc.arg(lim) OFFSET sqlc.arg(off);
|
||||||
|
-- name: UpdateMapRunProgress :one
|
||||||
|
UPDATE map_runs
|
||||||
|
SET status = sqlc.arg(status),
|
||||||
|
queued_items = sqlc.arg(queued_items),
|
||||||
|
running_items = sqlc.arg(running_items),
|
||||||
|
succeeded_items = sqlc.arg(succeeded_items),
|
||||||
|
failed_items = sqlc.arg(failed_items),
|
||||||
|
last_error = sqlc.arg(last_error),
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
completed_at = sqlc.arg(completed_at)
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
-- name: InsertMapItem :one
|
||||||
|
INSERT INTO map_items (
|
||||||
|
id,
|
||||||
|
run_id,
|
||||||
|
item_index,
|
||||||
|
status,
|
||||||
|
attempts,
|
||||||
|
last_error,
|
||||||
|
input_fb,
|
||||||
|
output_fb,
|
||||||
|
input_hash,
|
||||||
|
output_hash
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
sqlc.arg(id),
|
||||||
|
sqlc.arg(run_id),
|
||||||
|
sqlc.arg(item_index),
|
||||||
|
sqlc.arg(status),
|
||||||
|
sqlc.arg(attempts),
|
||||||
|
sqlc.arg(last_error),
|
||||||
|
sqlc.arg(input_fb),
|
||||||
|
sqlc.arg(output_fb),
|
||||||
|
sqlc.arg(input_hash),
|
||||||
|
sqlc.arg(output_hash)
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
-- name: GetMapItemByID :one
|
||||||
|
SELECT *
|
||||||
|
FROM map_items
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: GetMapItemByRunAndIndex :one
|
||||||
|
SELECT *
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = sqlc.arg(run_id)
|
||||||
|
AND item_index = sqlc.arg(item_index)
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: ListMapItemsByRunPaged :many
|
||||||
|
SELECT *
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = sqlc.arg(run_id)
|
||||||
|
ORDER BY item_index ASC
|
||||||
|
LIMIT sqlc.arg(lim) OFFSET sqlc.arg(off);
|
||||||
|
-- name: MarkMapItemRunning :one
|
||||||
|
UPDATE map_items
|
||||||
|
SET status = 'running',
|
||||||
|
attempts = attempts + 1,
|
||||||
|
last_error = NULL,
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
-- name: MarkMapItemSucceeded :one
|
||||||
|
UPDATE map_items
|
||||||
|
SET status = 'succeeded',
|
||||||
|
output_fb = sqlc.arg(output_fb),
|
||||||
|
output_hash = sqlc.arg(output_hash),
|
||||||
|
last_error = NULL,
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
-- name: MarkMapItemFailed :one
|
||||||
|
UPDATE map_items
|
||||||
|
SET status = 'failed',
|
||||||
|
last_error = sqlc.arg(last_error),
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||||
|
completed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||||
|
WHERE id = sqlc.arg(id)
|
||||||
|
RETURNING *;
|
||||||
|
-- name: CountMapItemsByRun :one
|
||||||
|
SELECT count(*) AS count
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = sqlc.arg(run_id);
|
||||||
|
-- name: CountMapItemsByRunAndStatus :many
|
||||||
|
SELECT status,
|
||||||
|
count(*) AS count
|
||||||
|
FROM map_items
|
||||||
|
WHERE run_id = sqlc.arg(run_id)
|
||||||
|
GROUP BY status;
|
||||||
404
pkg/memory/store/retrieval_policy.go
Normal file
404
pkg/memory/store/retrieval_policy.go
Normal file
|
|
@ -0,0 +1,404 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/ids"
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/logger"
|
||||||
|
"github.com/ZanzyTHEbar/dragonscale/pkg/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
type retrievalMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
retrievalModeShadow retrievalMode = "shadow"
|
||||||
|
retrievalModePromoted retrievalMode = "promoted"
|
||||||
|
retrievalModeRollback retrievalMode = "rollback"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
retrievalPolicyStateKey = "memory:retrieval:policy_state"
|
||||||
|
retrievalPolicyGatesKey = "memory:retrieval:promotion_gates"
|
||||||
|
retrievalPolicyMetricsKey = "memory:retrieval:shadow_metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
type retrievalPolicyState struct {
|
||||||
|
Mode retrievalMode `json:"mode"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type retrievalPromotionGates struct {
|
||||||
|
MinSamples int `json:"min_samples"`
|
||||||
|
MinAugmentedSamples int `json:"min_augmented_samples"`
|
||||||
|
MinTop1Parity float64 `json:"min_top1_parity"`
|
||||||
|
MinOverlapAtK float64 `json:"min_overlap_at_k"`
|
||||||
|
MinPromotedSamples int `json:"min_promoted_samples"`
|
||||||
|
RollbackTop1Parity float64 `json:"rollback_top1_parity"`
|
||||||
|
RollbackOverlapAtK float64 `json:"rollback_overlap_at_k"`
|
||||||
|
MaxNoResultRate float64 `json:"max_no_result_rate"`
|
||||||
|
PromotedNoResultRate float64 `json:"promoted_no_result_rate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type retrievalShadowMetrics struct {
|
||||||
|
TotalQueries int `json:"total_queries"`
|
||||||
|
NoResultQueries int `json:"no_result_queries"`
|
||||||
|
AugmentedQueries int `json:"augmented_queries"`
|
||||||
|
Top1ParityHits int `json:"top1_parity_hits"`
|
||||||
|
OverlapAtKTotal float64 `json:"overlap_at_k_total"`
|
||||||
|
PromotedQueries int `json:"promoted_queries"`
|
||||||
|
PromotedTop1Hits int `json:"promoted_top1_hits"`
|
||||||
|
PromotedOverlap float64 `json:"promoted_overlap_total"`
|
||||||
|
PromotedNoResultQueries int `json:"promoted_no_result_queries"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type retrievalParity struct {
|
||||||
|
Top1Match bool
|
||||||
|
OverlapAtK float64
|
||||||
|
NoResultPair bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultRetrievalPolicyState() retrievalPolicyState {
|
||||||
|
return retrievalPolicyState{
|
||||||
|
Mode: retrievalModeShadow,
|
||||||
|
Reason: "default_shadow_bootstrap",
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultRetrievalPromotionGates() retrievalPromotionGates {
|
||||||
|
return retrievalPromotionGates{
|
||||||
|
MinSamples: 25,
|
||||||
|
MinAugmentedSamples: 10,
|
||||||
|
MinTop1Parity: 0.65,
|
||||||
|
MinOverlapAtK: 0.60,
|
||||||
|
MinPromotedSamples: 10,
|
||||||
|
RollbackTop1Parity: 0.45,
|
||||||
|
RollbackOverlapAtK: 0.35,
|
||||||
|
MaxNoResultRate: 0.90,
|
||||||
|
PromotedNoResultRate: 0.95,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultRetrievalShadowMetrics() retrievalShadowMetrics {
|
||||||
|
return retrievalShadowMetrics{
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStore) loadRetrievalPolicyState(ctx context.Context) retrievalPolicyState {
|
||||||
|
raw, err := m.delegate.GetKV(ctx, m.agentID, retrievalPolicyStateKey)
|
||||||
|
if err != nil || raw == "" {
|
||||||
|
state := defaultRetrievalPolicyState()
|
||||||
|
if perr := m.persistRetrievalPolicyState(ctx, state); perr != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist default retrieval policy state",
|
||||||
|
map[string]interface{}{"error": perr.Error()})
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
var state retrievalPolicyState
|
||||||
|
if err := json.Unmarshal([]byte(raw), &state); err != nil {
|
||||||
|
state = defaultRetrievalPolicyState()
|
||||||
|
if perr := m.persistRetrievalPolicyState(ctx, state); perr != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist repaired retrieval policy state",
|
||||||
|
map[string]interface{}{"error": perr.Error()})
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
if !isValidRetrievalMode(state.Mode) {
|
||||||
|
state = defaultRetrievalPolicyState()
|
||||||
|
if perr := m.persistRetrievalPolicyState(ctx, state); perr != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist healed retrieval policy state",
|
||||||
|
map[string]interface{}{"error": perr.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStore) loadRetrievalPromotionGates(ctx context.Context) retrievalPromotionGates {
|
||||||
|
raw, err := m.delegate.GetKV(ctx, m.agentID, retrievalPolicyGatesKey)
|
||||||
|
if err != nil || raw == "" {
|
||||||
|
gates := defaultRetrievalPromotionGates()
|
||||||
|
if perr := m.persistRetrievalPromotionGates(ctx, gates); perr != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist default retrieval promotion gates",
|
||||||
|
map[string]interface{}{"error": perr.Error()})
|
||||||
|
}
|
||||||
|
return gates
|
||||||
|
}
|
||||||
|
|
||||||
|
var gates retrievalPromotionGates
|
||||||
|
if err := json.Unmarshal([]byte(raw), &gates); err != nil {
|
||||||
|
gates = defaultRetrievalPromotionGates()
|
||||||
|
if perr := m.persistRetrievalPromotionGates(ctx, gates); perr != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist repaired retrieval promotion gates",
|
||||||
|
map[string]interface{}{"error": perr.Error()})
|
||||||
|
}
|
||||||
|
return gates
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard-clauses for malformed/partial gate records.
|
||||||
|
if gates.MinSamples <= 0 {
|
||||||
|
gates.MinSamples = defaultRetrievalPromotionGates().MinSamples
|
||||||
|
}
|
||||||
|
if gates.MinPromotedSamples <= 0 {
|
||||||
|
gates.MinPromotedSamples = defaultRetrievalPromotionGates().MinPromotedSamples
|
||||||
|
}
|
||||||
|
if gates.MinAugmentedSamples <= 0 {
|
||||||
|
gates.MinAugmentedSamples = defaultRetrievalPromotionGates().MinAugmentedSamples
|
||||||
|
}
|
||||||
|
if gates.MinTop1Parity <= 0 {
|
||||||
|
gates.MinTop1Parity = defaultRetrievalPromotionGates().MinTop1Parity
|
||||||
|
}
|
||||||
|
if gates.MinOverlapAtK <= 0 {
|
||||||
|
gates.MinOverlapAtK = defaultRetrievalPromotionGates().MinOverlapAtK
|
||||||
|
}
|
||||||
|
if gates.RollbackTop1Parity <= 0 {
|
||||||
|
gates.RollbackTop1Parity = defaultRetrievalPromotionGates().RollbackTop1Parity
|
||||||
|
}
|
||||||
|
if gates.RollbackOverlapAtK <= 0 {
|
||||||
|
gates.RollbackOverlapAtK = defaultRetrievalPromotionGates().RollbackOverlapAtK
|
||||||
|
}
|
||||||
|
if gates.MaxNoResultRate <= 0 {
|
||||||
|
gates.MaxNoResultRate = defaultRetrievalPromotionGates().MaxNoResultRate
|
||||||
|
}
|
||||||
|
if gates.PromotedNoResultRate <= 0 {
|
||||||
|
gates.PromotedNoResultRate = defaultRetrievalPromotionGates().PromotedNoResultRate
|
||||||
|
}
|
||||||
|
return gates
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStore) loadRetrievalShadowMetrics(ctx context.Context) retrievalShadowMetrics {
|
||||||
|
raw, err := m.delegate.GetKV(ctx, m.agentID, retrievalPolicyMetricsKey)
|
||||||
|
if err != nil || raw == "" {
|
||||||
|
metrics := defaultRetrievalShadowMetrics()
|
||||||
|
if perr := m.persistRetrievalShadowMetrics(ctx, metrics); perr != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist default retrieval shadow metrics",
|
||||||
|
map[string]interface{}{"error": perr.Error()})
|
||||||
|
}
|
||||||
|
return metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
var metrics retrievalShadowMetrics
|
||||||
|
if err := json.Unmarshal([]byte(raw), &metrics); err != nil {
|
||||||
|
metrics = defaultRetrievalShadowMetrics()
|
||||||
|
if perr := m.persistRetrievalShadowMetrics(ctx, metrics); perr != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist repaired retrieval shadow metrics",
|
||||||
|
map[string]interface{}{"error": perr.Error()})
|
||||||
|
}
|
||||||
|
return metrics
|
||||||
|
}
|
||||||
|
return metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStore) persistRetrievalPolicyState(ctx context.Context, state retrievalPolicyState) error {
|
||||||
|
payload, err := json.Marshal(state)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal retrieval policy state: %w", err)
|
||||||
|
}
|
||||||
|
if err := m.delegate.UpsertKV(ctx, m.agentID, retrievalPolicyStateKey, string(payload)); err != nil {
|
||||||
|
return fmt.Errorf("upsert retrieval policy state: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStore) persistRetrievalPromotionGates(ctx context.Context, gates retrievalPromotionGates) error {
|
||||||
|
payload, err := json.Marshal(gates)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal retrieval promotion gates: %w", err)
|
||||||
|
}
|
||||||
|
if err := m.delegate.UpsertKV(ctx, m.agentID, retrievalPolicyGatesKey, string(payload)); err != nil {
|
||||||
|
return fmt.Errorf("upsert retrieval promotion gates: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStore) persistRetrievalShadowMetrics(ctx context.Context, metrics retrievalShadowMetrics) error {
|
||||||
|
payload, err := json.Marshal(metrics)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal retrieval shadow metrics: %w", err)
|
||||||
|
}
|
||||||
|
if err := m.delegate.UpsertKV(ctx, m.agentID, retrievalPolicyMetricsKey, string(payload)); err != nil {
|
||||||
|
return fmt.Errorf("upsert retrieval shadow metrics: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeRetrievalParity(baseline, augmented []memory.SearchResult, k int) retrievalParity {
|
||||||
|
if k <= 0 {
|
||||||
|
k = 5
|
||||||
|
}
|
||||||
|
parity := retrievalParity{
|
||||||
|
NoResultPair: len(baseline) == 0 && len(augmented) == 0,
|
||||||
|
}
|
||||||
|
if len(baseline) > 0 && len(augmented) > 0 {
|
||||||
|
parity.Top1Match = baseline[0].ID == augmented[0].ID
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTop := topKResultIDs(baseline, k)
|
||||||
|
augTop := topKResultIDs(augmented, k)
|
||||||
|
if len(baseTop) == 0 && len(augTop) == 0 {
|
||||||
|
parity.OverlapAtK = 1.0
|
||||||
|
return parity
|
||||||
|
}
|
||||||
|
|
||||||
|
union := make(map[ids.UUID]bool, len(baseTop)+len(augTop))
|
||||||
|
intersection := 0
|
||||||
|
for id := range baseTop {
|
||||||
|
union[id] = true
|
||||||
|
if augTop[id] {
|
||||||
|
intersection++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id := range augTop {
|
||||||
|
union[id] = true
|
||||||
|
}
|
||||||
|
if len(union) > 0 {
|
||||||
|
parity.OverlapAtK = float64(intersection) / float64(len(union))
|
||||||
|
}
|
||||||
|
return parity
|
||||||
|
}
|
||||||
|
|
||||||
|
func topKResultIDs(results []memory.SearchResult, k int) map[ids.UUID]bool {
|
||||||
|
if k <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(results) < k {
|
||||||
|
k = len(results)
|
||||||
|
}
|
||||||
|
out := make(map[ids.UUID]bool, k)
|
||||||
|
for i := 0; i < k; i++ {
|
||||||
|
out[results[i].ID] = true
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MemoryStore) updateRetrievalPolicy(
|
||||||
|
ctx context.Context,
|
||||||
|
state retrievalPolicyState,
|
||||||
|
gates retrievalPromotionGates,
|
||||||
|
metrics retrievalShadowMetrics,
|
||||||
|
parity retrievalParity,
|
||||||
|
augmentedUsed bool,
|
||||||
|
baseline []memory.SearchResult,
|
||||||
|
) retrievalPolicyState {
|
||||||
|
prevMode := state.Mode
|
||||||
|
|
||||||
|
metrics.TotalQueries++
|
||||||
|
if len(baseline) == 0 {
|
||||||
|
metrics.NoResultQueries++
|
||||||
|
}
|
||||||
|
if augmentedUsed {
|
||||||
|
metrics.AugmentedQueries++
|
||||||
|
}
|
||||||
|
if parity.Top1Match {
|
||||||
|
metrics.Top1ParityHits++
|
||||||
|
}
|
||||||
|
metrics.OverlapAtKTotal += parity.OverlapAtK
|
||||||
|
metrics.UpdatedAt = time.Now().UTC()
|
||||||
|
|
||||||
|
switch state.Mode {
|
||||||
|
case retrievalModeShadow:
|
||||||
|
top1Rate := safeRate(metrics.Top1ParityHits, metrics.TotalQueries)
|
||||||
|
overlapAvg := safeAvg(metrics.OverlapAtKTotal, metrics.TotalQueries)
|
||||||
|
noResultRate := safeRate(metrics.NoResultQueries, metrics.TotalQueries)
|
||||||
|
|
||||||
|
if metrics.TotalQueries >= gates.MinSamples &&
|
||||||
|
metrics.AugmentedQueries >= gates.MinAugmentedSamples &&
|
||||||
|
top1Rate >= gates.MinTop1Parity &&
|
||||||
|
overlapAvg >= gates.MinOverlapAtK &&
|
||||||
|
noResultRate <= gates.MaxNoResultRate {
|
||||||
|
state.Mode = retrievalModePromoted
|
||||||
|
state.Reason = fmt.Sprintf(
|
||||||
|
"promotion_gates_passed(top1=%.3f overlap=%.3f samples=%d)",
|
||||||
|
top1Rate,
|
||||||
|
overlapAvg,
|
||||||
|
metrics.TotalQueries,
|
||||||
|
)
|
||||||
|
state.UpdatedAt = time.Now().UTC()
|
||||||
|
metrics.PromotedQueries = 0
|
||||||
|
metrics.PromotedTop1Hits = 0
|
||||||
|
metrics.PromotedOverlap = 0
|
||||||
|
metrics.PromotedNoResultQueries = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case retrievalModePromoted:
|
||||||
|
metrics.PromotedQueries++
|
||||||
|
if len(baseline) == 0 {
|
||||||
|
metrics.PromotedNoResultQueries++
|
||||||
|
}
|
||||||
|
if parity.Top1Match {
|
||||||
|
metrics.PromotedTop1Hits++
|
||||||
|
}
|
||||||
|
metrics.PromotedOverlap += parity.OverlapAtK
|
||||||
|
|
||||||
|
if metrics.PromotedQueries >= gates.MinPromotedSamples {
|
||||||
|
promotedTop1 := safeRate(metrics.PromotedTop1Hits, metrics.PromotedQueries)
|
||||||
|
promotedOverlap := safeAvg(metrics.PromotedOverlap, metrics.PromotedQueries)
|
||||||
|
promotedNoResultRate := safeRate(metrics.PromotedNoResultQueries, metrics.PromotedQueries)
|
||||||
|
|
||||||
|
if promotedTop1 < gates.RollbackTop1Parity ||
|
||||||
|
promotedOverlap < gates.RollbackOverlapAtK ||
|
||||||
|
promotedNoResultRate > gates.PromotedNoResultRate {
|
||||||
|
state.Mode = retrievalModeRollback
|
||||||
|
state.Reason = fmt.Sprintf(
|
||||||
|
"rollback_triggered(top1=%.3f overlap=%.3f no_result_rate=%.3f promoted_samples=%d)",
|
||||||
|
promotedTop1,
|
||||||
|
promotedOverlap,
|
||||||
|
promotedNoResultRate,
|
||||||
|
metrics.PromotedQueries,
|
||||||
|
)
|
||||||
|
state.UpdatedAt = time.Now().UTC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevMode != state.Mode {
|
||||||
|
logger.InfoCF("memory", "retrieval policy mode transition",
|
||||||
|
map[string]interface{}{
|
||||||
|
"from": prevMode,
|
||||||
|
"to": state.Mode,
|
||||||
|
"reason": state.Reason,
|
||||||
|
"total_queries": metrics.TotalQueries,
|
||||||
|
"augmented_queries": metrics.AugmentedQueries,
|
||||||
|
"promoted_queries": metrics.PromotedQueries,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.persistRetrievalShadowMetrics(ctx, metrics); err != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist retrieval shadow metrics",
|
||||||
|
map[string]interface{}{"mode": state.Mode, "error": err.Error()})
|
||||||
|
}
|
||||||
|
if err := m.persistRetrievalPolicyState(ctx, state); err != nil {
|
||||||
|
logger.WarnCF("memory", "failed to persist retrieval policy state",
|
||||||
|
map[string]interface{}{"mode": state.Mode, "error": err.Error()})
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeRate(numerator, denominator int) float64 {
|
||||||
|
if denominator <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float64(numerator) / float64(denominator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeAvg(total float64, count int) float64 {
|
||||||
|
if count <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return total / float64(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidRetrievalMode(mode retrievalMode) bool {
|
||||||
|
switch mode {
|
||||||
|
case retrievalModeShadow, retrievalModePromoted, retrievalModeRollback:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
18
pkg/memory/store/token_test_helpers_test.go
Normal file
18
pkg/memory/store/token_test_helpers_test.go
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func contentAtLeastTokens(target int) string {
|
||||||
|
if target <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
for i := 0; estimateTokens(b.String()) < target; i++ {
|
||||||
|
b.WriteString(fmt.Sprintf("token-%d ", i))
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue