feat(Phase 4): Memory & Intelligence — memory, log, summarization, skills (#91)
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #91.
This commit is contained in:
@@ -3,7 +3,18 @@
|
||||
* drizzle-kit reads this file directly (avoids CJS/ESM extension issues).
|
||||
*/
|
||||
|
||||
import { pgTable, text, timestamp, boolean, uuid, jsonb, index } from 'drizzle-orm/pg-core';
|
||||
import {
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
boolean,
|
||||
uuid,
|
||||
jsonb,
|
||||
index,
|
||||
real,
|
||||
integer,
|
||||
customType,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
|
||||
// ─── Auth (BetterAuth-compatible) ────────────────────────────────────────────
|
||||
|
||||
@@ -211,3 +222,152 @@ export const messages = pgTable(
|
||||
},
|
||||
(t) => [index('messages_conversation_id_idx').on(t.conversationId)],
|
||||
);
|
||||
|
||||
// ─── pgvector custom type ───────────────────────────────────────────────────
|
||||
|
||||
const vector = customType<{ data: number[]; driverParam: string; config: { dimensions: number } }>({
|
||||
dataType(config) {
|
||||
return `vector(${config?.dimensions ?? 1536})`;
|
||||
},
|
||||
fromDriver(value: unknown): number[] {
|
||||
const str = value as string;
|
||||
return str
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map((v) => Number(v));
|
||||
},
|
||||
toDriver(value: number[]): string {
|
||||
return `[${value.join(',')}]`;
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Memory ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export const preferences = pgTable(
|
||||
'preferences',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: text('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
key: text('key').notNull(),
|
||||
value: jsonb('value').notNull(),
|
||||
category: text('category', {
|
||||
enum: ['communication', 'coding', 'workflow', 'appearance', 'general'],
|
||||
})
|
||||
.notNull()
|
||||
.default('general'),
|
||||
source: text('source'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [
|
||||
index('preferences_user_id_idx').on(t.userId),
|
||||
index('preferences_user_key_idx').on(t.userId, t.key),
|
||||
],
|
||||
);
|
||||
|
||||
export const insights = pgTable(
|
||||
'insights',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: text('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
content: text('content').notNull(),
|
||||
embedding: vector('embedding', { dimensions: 1536 }),
|
||||
source: text('source', {
|
||||
enum: ['agent', 'user', 'summarization', 'system'],
|
||||
})
|
||||
.notNull()
|
||||
.default('agent'),
|
||||
category: text('category', {
|
||||
enum: ['decision', 'learning', 'preference', 'fact', 'pattern', 'general'],
|
||||
})
|
||||
.notNull()
|
||||
.default('general'),
|
||||
relevanceScore: real('relevance_score').notNull().default(1.0),
|
||||
metadata: jsonb('metadata'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
decayedAt: timestamp('decayed_at', { withTimezone: true }),
|
||||
},
|
||||
(t) => [
|
||||
index('insights_user_id_idx').on(t.userId),
|
||||
index('insights_category_idx').on(t.category),
|
||||
index('insights_relevance_idx').on(t.relevanceScore),
|
||||
],
|
||||
);
|
||||
|
||||
// ─── Agent Logs ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const agentLogs = pgTable(
|
||||
'agent_logs',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
sessionId: text('session_id').notNull(),
|
||||
userId: text('user_id').references(() => users.id, { onDelete: 'set null' }),
|
||||
level: text('level', { enum: ['debug', 'info', 'warn', 'error'] })
|
||||
.notNull()
|
||||
.default('info'),
|
||||
category: text('category', {
|
||||
enum: ['decision', 'tool_use', 'learning', 'error', 'general'],
|
||||
})
|
||||
.notNull()
|
||||
.default('general'),
|
||||
content: text('content').notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
tier: text('tier', { enum: ['hot', 'warm', 'cold'] })
|
||||
.notNull()
|
||||
.default('hot'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
summarizedAt: timestamp('summarized_at', { withTimezone: true }),
|
||||
archivedAt: timestamp('archived_at', { withTimezone: true }),
|
||||
},
|
||||
(t) => [
|
||||
index('agent_logs_session_id_idx').on(t.sessionId),
|
||||
index('agent_logs_user_id_idx').on(t.userId),
|
||||
index('agent_logs_tier_idx').on(t.tier),
|
||||
index('agent_logs_created_at_idx').on(t.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
// ─── Skills ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export const skills = pgTable(
|
||||
'skills',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull().unique(),
|
||||
description: text('description'),
|
||||
version: text('version'),
|
||||
source: text('source', { enum: ['builtin', 'community', 'custom'] })
|
||||
.notNull()
|
||||
.default('custom'),
|
||||
config: jsonb('config'),
|
||||
enabled: boolean('enabled').notNull().default(true),
|
||||
installedBy: text('installed_by').references(() => users.id, { onDelete: 'set null' }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [index('skills_enabled_idx').on(t.enabled)],
|
||||
);
|
||||
|
||||
// ─── Summarization Jobs ─────────────────────────────────────────────────────
|
||||
|
||||
export const summarizationJobs = pgTable(
|
||||
'summarization_jobs',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
status: text('status', { enum: ['pending', 'running', 'completed', 'failed'] })
|
||||
.notNull()
|
||||
.default('pending'),
|
||||
logsProcessed: integer('logs_processed').notNull().default(0),
|
||||
insightsCreated: integer('insights_created').notNull().default(0),
|
||||
errorMessage: text('error_message'),
|
||||
startedAt: timestamp('started_at', { withTimezone: true }),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [index('summarization_jobs_status_idx').on(t.status)],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user