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:
2026-03-13 13:56:50 +00:00
committed by jason.woltje
parent d83ebe65e9
commit 9eb48e1d9b
35 changed files with 1481 additions and 16 deletions

View File

@@ -1,4 +1,18 @@
export { createDb, type Db, type DbHandle } from './client.js';
export { runMigrations } from './migrate.js';
export * from './schema.js';
export { eq, and, or, desc, asc, sql, inArray, isNull, isNotNull } from 'drizzle-orm';
export {
eq,
and,
or,
desc,
asc,
sql,
inArray,
isNull,
isNotNull,
gt,
lt,
gte,
lte,
} from 'drizzle-orm';

View File

@@ -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)],
);