feat(memory): add migrations for immutable messages, edges, soft delete, RL schema

This commit is contained in:
ZanzyTHEbar 2026-03-04 18:41:09 +00:00
parent 3ce3faed42
commit 89c8f17aa9
4 changed files with 213 additions and 0 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}