diff --git a/pkg/memory/migrations/013_immutable_messages.go b/pkg/memory/migrations/013_immutable_messages.go new file mode 100644 index 000000000..04f38ce3d --- /dev/null +++ b/pkg/memory/migrations/013_immutable_messages.go @@ -0,0 +1,50 @@ +package migrations + +import ( + "context" + "database/sql" + "fmt" + + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(up013ImmutableMessages, down013ImmutableMessages) +} + +func up013ImmutableMessages(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + `CREATE TABLE IF NOT EXISTS immutable_messages ( + id BLOB PRIMARY KEY, + session_key TEXT NOT NULL, + role TEXT NOT NULL, + content TEXT NOT NULL, + tool_call_id TEXT NOT NULL DEFAULT '', + tool_calls TEXT NOT NULL DEFAULT '', + token_estimate INTEGER NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +)`, + `CREATE INDEX IF NOT EXISTS idx_immutable_session_created ON immutable_messages(session_key, created_at)`, + `CREATE INDEX IF NOT EXISTS idx_immutable_token_estimate ON immutable_messages(token_estimate)`, + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("013_immutable_messages up: %w\nSQL: %s", err, s) + } + } + return nil +} + +func down013ImmutableMessages(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + `DROP INDEX IF EXISTS idx_immutable_token_estimate`, + `DROP INDEX IF EXISTS idx_immutable_session_created`, + `DROP TABLE IF EXISTS immutable_messages`, + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("013_immutable_messages down: %w\nSQL: %s", err, s) + } + } + return nil +} diff --git a/pkg/memory/migrations/014_memory_edges.go b/pkg/memory/migrations/014_memory_edges.go new file mode 100644 index 000000000..ea18178aa --- /dev/null +++ b/pkg/memory/migrations/014_memory_edges.go @@ -0,0 +1,52 @@ +package migrations + +import ( + "context" + "database/sql" + "fmt" + + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(up014MemoryEdges, down014MemoryEdges) +} + +func up014MemoryEdges(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + `CREATE TABLE IF NOT EXISTS memory_edges ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + from_id BLOB NOT NULL, + to_id BLOB NOT NULL, + edge_type TEXT NOT NULL DEFAULT 'related_to', + weight REAL NOT NULL DEFAULT 1.0, + created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) +)`, + `CREATE INDEX IF NOT EXISTS idx_memory_edges_from ON memory_edges(from_id)`, + `CREATE INDEX IF NOT EXISTS idx_memory_edges_to ON memory_edges(to_id)`, + `CREATE INDEX IF NOT EXISTS idx_memory_edges_type ON memory_edges(edge_type)`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_edges_pair ON memory_edges(from_id, to_id)`, + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("014_memory_edges up: %w\nSQL: %s", err, s) + } + } + return nil +} + +func down014MemoryEdges(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + `DROP INDEX IF EXISTS idx_memory_edges_pair`, + `DROP INDEX IF EXISTS idx_memory_edges_type`, + `DROP INDEX IF EXISTS idx_memory_edges_to`, + `DROP INDEX IF EXISTS idx_memory_edges_from`, + `DROP TABLE IF EXISTS memory_edges`, + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("014_memory_edges down: %w\nSQL: %s", err, s) + } + } + return nil +} diff --git a/pkg/memory/migrations/015_soft_delete.go b/pkg/memory/migrations/015_soft_delete.go new file mode 100644 index 000000000..1fe039d1f --- /dev/null +++ b/pkg/memory/migrations/015_soft_delete.go @@ -0,0 +1,46 @@ +package migrations + +import ( + "context" + "database/sql" + "fmt" + + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(up015SoftDelete, down015SoftDelete) +} + +func up015SoftDelete(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + // Add suppressed_at column to recall_items for soft delete + `ALTER TABLE recall_items ADD COLUMN suppressed_at DATETIME`, + `CREATE INDEX IF NOT EXISTS idx_recall_suppressed ON recall_items(suppressed_at)`, + + // Add suppressed_at column to archival_chunks for soft delete + `ALTER TABLE archival_chunks ADD COLUMN suppressed_at DATETIME`, + `CREATE INDEX IF NOT EXISTS idx_chunks_suppressed ON archival_chunks(suppressed_at)`, + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("015_soft_delete up: %w\nSQL: %s", err, s) + } + } + return nil +} + +func down015SoftDelete(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + `DROP INDEX IF EXISTS idx_recall_suppressed`, + `DROP INDEX IF EXISTS idx_chunks_suppressed`, + // SQLite doesn't support DROP COLUMN directly; would need table recreation + // Leaving columns as-is for safety; they are nullable and ignored by queries + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("015_soft_delete down: %w\nSQL: %s", err, s) + } + } + return nil +} diff --git a/pkg/memory/migrations/016_rl_schema.go b/pkg/memory/migrations/016_rl_schema.go new file mode 100644 index 000000000..c759764d5 --- /dev/null +++ b/pkg/memory/migrations/016_rl_schema.go @@ -0,0 +1,65 @@ +package migrations + +import ( + "context" + "database/sql" + "fmt" + + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(up016RLSchema, down016RLSchema) +} + +func up016RLSchema(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + // Create task_baselines table for RL statistics per agent + `CREATE TABLE IF NOT EXISTS task_baselines ( + agent_id TEXT PRIMARY KEY, + count INTEGER DEFAULT 0, + mean_tokens INTEGER DEFAULT 0, + mean_errors REAL DEFAULT 0, + mean_user_corrections REAL DEFAULT 0, + m2_tokens REAL DEFAULT 0, + m2_errors REAL DEFAULT 0, + m2_user_corrections REAL DEFAULT 0, + updated_at DATETIME + )`, + // Add RL-related columns to recall_items + `ALTER TABLE recall_items ADD COLUMN rl_weight REAL DEFAULT 1.0`, + `ALTER TABLE recall_items ADD COLUMN rl_credit REAL`, + `ALTER TABLE recall_items ADD COLUMN self_report_score INTEGER`, + `ALTER TABLE recall_items ADD COLUMN task_retrieval_count INTEGER DEFAULT 0`, + // Create index for RL weight lookups + `CREATE INDEX IF NOT EXISTS idx_recall_rl_weight ON recall_items(rl_weight)`, + // Create index for task retrieval tracking + `CREATE INDEX IF NOT EXISTS idx_recall_task_retrieval ON recall_items(task_retrieval_count)`, + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("016_rl_schema up: %w\nSQL: %s", err, s) + } + } + return nil +} + +func down016RLSchema(ctx context.Context, tx *sql.Tx) error { + stmts := []string{ + // Drop indexes first + `DROP INDEX IF EXISTS idx_recall_task_retrieval`, + `DROP INDEX IF EXISTS idx_recall_rl_weight`, + // Drop task_baselines table + `DROP TABLE IF EXISTS task_baselines`, + // Note: SQLite doesn't support DROP COLUMN directly + // The columns (rl_weight, rl_credit, self_report_score, task_retrieval_count) + // are left in place with defaults to avoid breaking existing data. + // They will be ignored by queries that don't reference them. + } + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("016_rl_schema down: %w\nSQL: %s", err, s) + } + } + return nil +}