mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-18 20:31:37 +00:00
Adds a core sessions_search agent tool backed by a SQLite FTS5 index that lives next to the transcript rows in the per-agent database. Indexing is transactional: user/assistant text is indexed inside the same write transaction that persists the event, deletes drop index rows in the same transaction, and branch rewinds mark the session dirty so the next search rebuilds it from the same visible-path resolution sessions_history uses (which also lazily backfills doctor-migrated databases). Search executes gateway-side behind an additive sessions.search method with a bounded session-key allowlist; visibility, sandbox restrictions, and snippet redaction mirror sessions_history. Closes #100978
307 lines
9.3 KiB
SQL
307 lines
9.3 KiB
SQL
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
meta_key TEXT NOT NULL PRIMARY KEY,
|
|
role TEXT NOT NULL,
|
|
schema_version INTEGER NOT NULL,
|
|
agent_id TEXT,
|
|
app_version TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
session_id TEXT NOT NULL PRIMARY KEY,
|
|
session_key TEXT NOT NULL,
|
|
session_scope TEXT NOT NULL DEFAULT 'conversation' CHECK (session_scope IN ('conversation', 'shared-main', 'group', 'channel')),
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
transcript_updated_at INTEGER DEFAULT NULL,
|
|
transcript_observed_at INTEGER DEFAULT NULL,
|
|
started_at INTEGER,
|
|
ended_at INTEGER,
|
|
status TEXT CHECK (status IS NULL OR status IN ('running', 'done', 'failed', 'killed', 'timeout')),
|
|
chat_type TEXT CHECK (chat_type IS NULL OR chat_type IN ('direct', 'group', 'channel')),
|
|
channel TEXT,
|
|
account_id TEXT,
|
|
primary_conversation_id TEXT,
|
|
model_provider TEXT,
|
|
model TEXT,
|
|
agent_harness_id TEXT,
|
|
parent_session_key TEXT,
|
|
spawned_by TEXT,
|
|
display_name TEXT,
|
|
FOREIGN KEY (primary_conversation_id) REFERENCES conversations(conversation_id) ON DELETE SET NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_updated_at
|
|
ON sessions(updated_at DESC, session_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_created_at
|
|
ON sessions(created_at DESC, session_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_conversation
|
|
ON sessions(primary_conversation_id, updated_at DESC, session_id)
|
|
WHERE primary_conversation_id IS NOT NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS session_routes (
|
|
session_key TEXT NOT NULL PRIMARY KEY,
|
|
session_id TEXT NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_session_routes_session_id
|
|
ON session_routes(session_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
conversation_id TEXT NOT NULL PRIMARY KEY,
|
|
channel TEXT NOT NULL,
|
|
account_id TEXT NOT NULL,
|
|
kind TEXT NOT NULL CHECK (kind IN ('direct', 'group', 'channel')),
|
|
peer_id TEXT NOT NULL,
|
|
parent_conversation_id TEXT,
|
|
thread_id TEXT,
|
|
native_channel_id TEXT,
|
|
native_direct_user_id TEXT,
|
|
label TEXT,
|
|
metadata_json TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_conversations_lookup
|
|
ON conversations(channel, account_id, kind, peer_id, thread_id);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_conversations_identity
|
|
ON conversations(
|
|
channel,
|
|
account_id,
|
|
kind,
|
|
peer_id,
|
|
IFNULL(parent_conversation_id, ''),
|
|
IFNULL(thread_id, '')
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_conversations_updated
|
|
ON conversations(updated_at DESC, conversation_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS session_conversations (
|
|
session_id TEXT NOT NULL,
|
|
conversation_id TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'primary' CHECK (role IN ('primary', 'participant', 'related')),
|
|
first_seen_at INTEGER NOT NULL,
|
|
last_seen_at INTEGER NOT NULL,
|
|
PRIMARY KEY (session_id, conversation_id, role),
|
|
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
|
|
FOREIGN KEY (conversation_id) REFERENCES conversations(conversation_id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_session_conversations_conversation
|
|
ON session_conversations(conversation_id, last_seen_at DESC, session_id);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_session_conversations_primary
|
|
ON session_conversations(session_id)
|
|
WHERE role = 'primary';
|
|
|
|
CREATE TABLE IF NOT EXISTS session_entries (
|
|
session_key TEXT NOT NULL PRIMARY KEY,
|
|
session_id TEXT NOT NULL,
|
|
entry_json TEXT NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_session_entries_updated_at
|
|
ON session_entries(updated_at DESC, session_key);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_session_entries_session_updated
|
|
ON session_entries(session_id, updated_at DESC, session_key);
|
|
|
|
CREATE TABLE IF NOT EXISTS transcript_events (
|
|
session_id TEXT NOT NULL,
|
|
seq INTEGER NOT NULL,
|
|
event_json TEXT NOT NULL,
|
|
created_at INTEGER NOT NULL,
|
|
PRIMARY KEY (session_id, seq),
|
|
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS trajectory_runtime_events (
|
|
session_id TEXT NOT NULL,
|
|
seq INTEGER NOT NULL,
|
|
run_id TEXT,
|
|
event_json TEXT NOT NULL,
|
|
created_at INTEGER NOT NULL,
|
|
PRIMARY KEY (session_id, seq),
|
|
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_trajectory_runtime_run
|
|
ON trajectory_runtime_events(session_id, run_id, seq)
|
|
WHERE run_id IS NOT NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS transcript_event_identities (
|
|
session_id TEXT NOT NULL,
|
|
event_id TEXT NOT NULL,
|
|
seq INTEGER NOT NULL,
|
|
event_type TEXT,
|
|
parent_id TEXT,
|
|
message_idempotency_key TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
PRIMARY KEY (session_id, event_id),
|
|
FOREIGN KEY (session_id, seq) REFERENCES transcript_events(session_id, seq) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_transcript_message_idempotency
|
|
ON transcript_event_identities(session_id, message_idempotency_key)
|
|
WHERE message_idempotency_key IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_transcript_event_parent
|
|
ON transcript_event_identities(session_id, parent_id)
|
|
WHERE parent_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_transcript_event_sequence
|
|
ON transcript_event_identities(session_id, event_type, seq DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS cache_entries (
|
|
scope TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
value_json TEXT,
|
|
blob BLOB,
|
|
expires_at INTEGER,
|
|
updated_at INTEGER NOT NULL,
|
|
PRIMARY KEY (scope, key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_cache_expiry
|
|
ON cache_entries(scope, expires_at, key)
|
|
WHERE expires_at IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_agent_cache_updated
|
|
ON cache_entries(scope, updated_at DESC, key);
|
|
|
|
CREATE TABLE IF NOT EXISTS auth_profile_store (
|
|
store_key TEXT NOT NULL PRIMARY KEY,
|
|
store_json TEXT NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS auth_profile_state (
|
|
state_key TEXT NOT NULL PRIMARY KEY,
|
|
state_json TEXT NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS memory_index_meta (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS memory_index_sources (
|
|
id INTEGER PRIMARY KEY,
|
|
path TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT 'memory',
|
|
hash TEXT NOT NULL,
|
|
mtime INTEGER NOT NULL,
|
|
size INTEGER NOT NULL,
|
|
UNIQUE (path, source)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS memory_index_chunks (
|
|
id TEXT PRIMARY KEY,
|
|
path TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT 'memory',
|
|
start_line INTEGER NOT NULL,
|
|
end_line INTEGER NOT NULL,
|
|
hash TEXT NOT NULL,
|
|
model TEXT NOT NULL,
|
|
text TEXT NOT NULL,
|
|
embedding TEXT NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS memory_embedding_cache (
|
|
provider TEXT NOT NULL,
|
|
model TEXT NOT NULL,
|
|
provider_key TEXT NOT NULL,
|
|
hash TEXT NOT NULL,
|
|
embedding TEXT NOT NULL,
|
|
dims INTEGER,
|
|
updated_at INTEGER NOT NULL,
|
|
PRIMARY KEY (provider, model, provider_key, hash)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS memory_index_state (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
revision INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS session_transcript_index_state (
|
|
session_id TEXT NOT NULL PRIMARY KEY,
|
|
indexed_seq INTEGER NOT NULL,
|
|
leaf_event_id TEXT,
|
|
needs_rebuild INTEGER NOT NULL DEFAULT 0,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS session_transcript_fts USING fts5(
|
|
text,
|
|
session_id UNINDEXED,
|
|
message_id UNINDEXED,
|
|
role UNINDEXED,
|
|
timestamp UNINDEXED,
|
|
tokenize = 'unicode61 remove_diacritics 2'
|
|
);
|
|
|
|
INSERT OR IGNORE INTO memory_index_state (id, revision) VALUES (1, 0);
|
|
|
|
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_insert
|
|
AFTER INSERT ON memory_index_sources
|
|
BEGIN
|
|
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_update
|
|
AFTER UPDATE ON memory_index_sources
|
|
BEGIN
|
|
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_delete
|
|
AFTER DELETE ON memory_index_sources
|
|
BEGIN
|
|
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_insert
|
|
AFTER INSERT ON memory_index_chunks
|
|
BEGIN
|
|
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_update
|
|
AFTER UPDATE ON memory_index_chunks
|
|
BEGIN
|
|
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_delete
|
|
AFTER DELETE ON memory_index_chunks
|
|
BEGIN
|
|
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
|
END;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_memory_embedding_cache_updated_at
|
|
ON memory_embedding_cache(updated_at);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_memory_index_sources_source
|
|
ON memory_index_sources(source);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_path_source
|
|
ON memory_index_chunks(path, source);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_path
|
|
ON memory_index_chunks(path);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_source
|
|
ON memory_index_chunks(source);
|