feat(memory/migrations): add Goose migrations 007-010
007 — agent_conversations + agent_messages (conversation turns)
008 — agent_runs + agent_run_states + agent_state_transitions +
agent_checkpoints + agent_tool_results (full runtime state)
009 — jobs queue with kind+dedupe_key partial unique index
010 — conversation graph: forks, links, threads, thread_messages,
mentions, message_revisions
All tables use BLOB PRIMARY KEY (UUIDv7), CASCADE deletes on FK,
and ISO8601 DATETIME defaults via strftime('%Y-%m-%dT%H:%M:%fZ').
Down migrations drop all created tables in reverse dependency order.
This commit is contained in:
parent
626308ad95
commit
4b1567f08b
4 changed files with 325 additions and 0 deletions
53
pkg/memory/migrations/007_agent_conversations.go
Normal file
53
pkg/memory/migrations/007_agent_conversations.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigrationContext(up007AgentConversations, down007AgentConversations)
|
||||
}
|
||||
|
||||
func up007AgentConversations(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`CREATE TABLE IF NOT EXISTS agent_conversations (
|
||||
id BLOB PRIMARY KEY,
|
||||
title 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'))
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS agent_messages (
|
||||
id BLOB PRIMARY KEY,
|
||||
conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
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_agent_messages_conversation_created_at ON agent_messages(conversation_id, created_at)`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("007_agent_conversations up: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func down007AgentConversations(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`DROP TABLE IF EXISTS agent_messages`,
|
||||
`DROP TABLE IF EXISTS agent_conversations`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("007_agent_conversations down: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
107
pkg/memory/migrations/008_agent_runtime_state.go
Normal file
107
pkg/memory/migrations/008_agent_runtime_state.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigrationContext(up008AgentRuntimeState, down008AgentRuntimeState)
|
||||
}
|
||||
|
||||
func up008AgentRuntimeState(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`CREATE TABLE IF NOT EXISTS agent_runs (
|
||||
id BLOB PRIMARY KEY,
|
||||
conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
status TEXT NOT NULL DEFAULT 'running',
|
||||
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_agent_runs_conversation_created_at ON agent_runs(conversation_id, created_at)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_run_states (
|
||||
id BLOB PRIMARY KEY,
|
||||
run_id BLOB NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE,
|
||||
step_index INTEGER NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
snapshot_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')),
|
||||
UNIQUE(run_id, step_index)
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_run_states_run_step ON agent_run_states(run_id, step_index)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_state_transitions (
|
||||
id BLOB PRIMARY KEY,
|
||||
run_id BLOB NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE,
|
||||
step_index INTEGER NOT NULL,
|
||||
from_state TEXT NOT NULL,
|
||||
to_state TEXT NOT NULL,
|
||||
trigger TEXT NOT NULL,
|
||||
at DATETIME NOT NULL,
|
||||
meta_json JSON NOT NULL DEFAULT '{}',
|
||||
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'))
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_state_transitions_run_step_at ON agent_state_transitions(run_id, step_index, at)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_checkpoints (
|
||||
id BLOB PRIMARY KEY,
|
||||
conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
run_state_id BLOB NOT NULL REFERENCES agent_run_states(id) ON DELETE RESTRICT,
|
||||
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')),
|
||||
UNIQUE(conversation_id, name)
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_checkpoints_conversation_created_at ON agent_checkpoints(conversation_id, created_at)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_tool_results (
|
||||
id BLOB PRIMARY KEY,
|
||||
conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
run_id BLOB NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE,
|
||||
step_index INTEGER NOT NULL,
|
||||
tool_call_id TEXT NOT NULL,
|
||||
tool_name TEXT NOT NULL,
|
||||
full_key TEXT NOT NULL,
|
||||
preview TEXT,
|
||||
chunk_count 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')),
|
||||
UNIQUE(run_id, tool_call_id)
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_tool_results_conversation_created_at ON agent_tool_results(conversation_id, created_at)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_tool_results_run_step ON agent_tool_results(run_id, step_index)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_tool_results_tool_name ON agent_tool_results(tool_name)`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("008_agent_runtime_state up: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func down008AgentRuntimeState(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`DROP TABLE IF EXISTS agent_tool_results`,
|
||||
`DROP TABLE IF EXISTS agent_checkpoints`,
|
||||
`DROP TABLE IF EXISTS agent_state_transitions`,
|
||||
`DROP TABLE IF EXISTS agent_run_states`,
|
||||
`DROP TABLE IF EXISTS agent_runs`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("008_agent_runtime_state down: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
54
pkg/memory/migrations/009_jobs.go
Normal file
54
pkg/memory/migrations/009_jobs.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigrationContext(up009Jobs, down009Jobs)
|
||||
}
|
||||
|
||||
func up009Jobs(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`CREATE TABLE IF NOT EXISTS jobs (
|
||||
id BLOB PRIMARY KEY,
|
||||
kind TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
run_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
max_attempts INTEGER NOT NULL DEFAULT 3,
|
||||
locked_at DATETIME,
|
||||
locked_by TEXT,
|
||||
payload_json JSON NOT NULL DEFAULT '{}',
|
||||
dedupe_key TEXT,
|
||||
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_jobs_status_run_at ON jobs(status, run_at)`,
|
||||
`CREATE UNIQUE INDEX IF NOT EXISTS idx_jobs_kind_dedupe ON jobs(kind, dedupe_key) WHERE dedupe_key IS NOT NULL`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("009_jobs up: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func down009Jobs(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`DROP TABLE IF EXISTS jobs`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("009_jobs down: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
111
pkg/memory/migrations/010_conversation_graph.go
Normal file
111
pkg/memory/migrations/010_conversation_graph.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigrationContext(up010ConversationGraph, down010ConversationGraph)
|
||||
}
|
||||
|
||||
func up010ConversationGraph(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`CREATE TABLE IF NOT EXISTS agent_conversation_forks (
|
||||
id BLOB PRIMARY KEY,
|
||||
parent_conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
child_conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
checkpoint_id BLOB NOT NULL REFERENCES agent_checkpoints(id) ON DELETE RESTRICT,
|
||||
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')),
|
||||
UNIQUE(child_conversation_id)
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_conversation_forks_parent_created_at ON agent_conversation_forks(parent_conversation_id, created_at DESC)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_conversation_links (
|
||||
id BLOB PRIMARY KEY,
|
||||
conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
linked_conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
kind TEXT NOT NULL DEFAULT 'merge',
|
||||
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')),
|
||||
UNIQUE(conversation_id, linked_conversation_id, kind)
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_conversation_links_conversation_id_created_at ON agent_conversation_links(conversation_id, created_at DESC)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_threads (
|
||||
id BLOB PRIMARY KEY,
|
||||
conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
title TEXT,
|
||||
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_agent_threads_conversation_id_created_at ON agent_threads(conversation_id, created_at DESC)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_thread_messages (
|
||||
id BLOB PRIMARY KEY,
|
||||
thread_id BLOB NOT NULL REFERENCES agent_threads(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
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_agent_thread_messages_thread_id_created_at ON agent_thread_messages(thread_id, created_at ASC)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_mentions (
|
||||
id BLOB PRIMARY KEY,
|
||||
conversation_id BLOB NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
||||
message_id BLOB REFERENCES agent_messages(id) ON DELETE SET NULL,
|
||||
kind TEXT NOT NULL,
|
||||
target_id BLOB NOT NULL,
|
||||
raw TEXT NOT NULL,
|
||||
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_agent_mentions_conversation_id_created_at ON agent_mentions(conversation_id, created_at DESC)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_agent_mentions_kind_target_id ON agent_mentions(kind, target_id)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS agent_message_revisions (
|
||||
id BLOB PRIMARY KEY,
|
||||
message_id BLOB NOT NULL REFERENCES agent_messages(id) ON DELETE CASCADE,
|
||||
editor TEXT NOT NULL,
|
||||
old_content TEXT NOT NULL,
|
||||
new_content TEXT NOT NULL,
|
||||
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_agent_message_revisions_message_id_created_at ON agent_message_revisions(message_id, created_at DESC)`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("010_conversation_graph up: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func down010ConversationGraph(_ context.Context, tx *sql.Tx) error {
|
||||
stmts := []string{
|
||||
`DROP TABLE IF EXISTS agent_message_revisions`,
|
||||
`DROP TABLE IF EXISTS agent_mentions`,
|
||||
`DROP TABLE IF EXISTS agent_thread_messages`,
|
||||
`DROP TABLE IF EXISTS agent_threads`,
|
||||
`DROP TABLE IF EXISTS agent_conversation_links`,
|
||||
`DROP TABLE IF EXISTS agent_conversation_forks`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
if _, err := tx.ExecContext(context.Background(), s); err != nil {
|
||||
return fmt.Errorf("010_conversation_graph down: %w\nSQL: %s", err, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue