feat(vibe): adapt sessions to context and exploration
This commit is contained in:
@@ -43,3 +43,19 @@ describe('Vibe durable session migration', () => {
|
||||
expect(migration!.sql).toContain('idx_vibe_events_user_occurred');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vibe context memory migration', () => {
|
||||
it('adds bounded session exploration state and exactly-once projection storage', () => {
|
||||
const migration = MIGRATIONS.find(({ id }) => id === '20260802_vibe_context_memory_exploration');
|
||||
expect(migration?.sql).toContain('CREATE TABLE IF NOT EXISTS vibe_session_profiles');
|
||||
expect(migration?.sql).toContain('exploration_coefficient');
|
||||
expect(migration?.sql).toContain('vibe_session_feedback_projections');
|
||||
});
|
||||
|
||||
it('backfills profiles for durable sessions created before context memory', () => {
|
||||
const migration = MIGRATIONS.find(({ id }) => id === '20260802_vibe_session_profile_backfill');
|
||||
expect(migration?.sql).toContain('INSERT INTO vibe_session_profiles');
|
||||
expect(migration?.sql).toContain('SELECT id, user_id FROM vibe_sessions');
|
||||
expect(migration?.sql).toContain('ON CONFLICT (session_id) DO NOTHING');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -720,4 +720,43 @@ export const MIGRATIONS: Migration[] = [
|
||||
);
|
||||
`,
|
||||
},
|
||||
{
|
||||
// Session-specific exploration, goals, and deliberately lossy session
|
||||
// fingerprints are derived from the immutable Vibe ledger. Keeping them
|
||||
// separate from listener_beliefs prevents a transient session from
|
||||
// rewriting permanent taste.
|
||||
id: '20260802_vibe_context_memory_exploration',
|
||||
sql: `
|
||||
CREATE TABLE IF NOT EXISTS vibe_session_profiles (
|
||||
session_id UUID PRIMARY KEY REFERENCES vibe_sessions(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL,
|
||||
fingerprint JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
goals JSONB NOT NULL DEFAULT '{"type":"discovery","target":1,"progress":0}'::jsonb,
|
||||
exploration_coefficient REAL NOT NULL DEFAULT 0.30
|
||||
CHECK (exploration_coefficient >= 0 AND exploration_coefficient <= 1),
|
||||
discovery_radius REAL NOT NULL DEFAULT 0.38
|
||||
CHECK (discovery_radius >= 0 AND discovery_radius <= 1),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_vibe_session_profiles_user_updated
|
||||
ON vibe_session_profiles (user_id, updated_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vibe_session_feedback_projections (
|
||||
event_id UUID PRIMARY KEY REFERENCES vibe_events(id) ON DELETE CASCADE,
|
||||
projected_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
`,
|
||||
},
|
||||
{
|
||||
// The profile table was introduced after durable sessions. Backfill every
|
||||
// pre-existing session before feedback can claim its exactly-once marker;
|
||||
// newly created sessions receive their context-derived initial goals in
|
||||
// createVibeSession's transaction.
|
||||
id: '20260802_vibe_session_profile_backfill',
|
||||
sql: `
|
||||
INSERT INTO vibe_session_profiles (session_id, user_id)
|
||||
SELECT id, user_id FROM vibe_sessions
|
||||
ON CONFLICT (session_id) DO NOTHING;
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -543,6 +543,23 @@ CREATE TABLE IF NOT EXISTS vibe_sessions (
|
||||
CREATE INDEX IF NOT EXISTS idx_vibe_sessions_user_last_event
|
||||
ON vibe_sessions (user_id, last_event_at DESC);
|
||||
|
||||
-- Compact, derived memory for the session director. Fingerprints are a
|
||||
-- deliberately lossy description of session shape (not a track list) and are
|
||||
-- used only as a soft freshness signal against recent sessions.
|
||||
CREATE TABLE IF NOT EXISTS vibe_session_profiles (
|
||||
session_id UUID PRIMARY KEY REFERENCES vibe_sessions(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL,
|
||||
fingerprint JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
goals JSONB NOT NULL DEFAULT '{"type":"discovery","target":1,"progress":0}'::jsonb,
|
||||
exploration_coefficient REAL NOT NULL DEFAULT 0.30
|
||||
CHECK (exploration_coefficient >= 0 AND exploration_coefficient <= 1),
|
||||
discovery_radius REAL NOT NULL DEFAULT 0.38
|
||||
CHECK (discovery_radius >= 0 AND discovery_radius <= 1),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_vibe_session_profiles_user_updated
|
||||
ON vibe_session_profiles (user_id, updated_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vibe_events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
client_event_id UUID,
|
||||
@@ -570,6 +587,14 @@ CREATE TABLE IF NOT EXISTS vibe_event_projections (
|
||||
projected_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Separate from listener-belief projection because exploration is session
|
||||
-- state. It lets a failed post-event replan safely retry the exact same
|
||||
-- adaptation without counting the feedback twice.
|
||||
CREATE TABLE IF NOT EXISTS vibe_session_feedback_projections (
|
||||
event_id UUID PRIMARY KEY REFERENCES vibe_events(id) ON DELETE CASCADE,
|
||||
projected_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vibe_plan_versions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
session_id UUID NOT NULL REFERENCES vibe_sessions(id) ON DELETE CASCADE,
|
||||
|
||||
@@ -258,3 +258,16 @@ export interface VibePlanItem {
|
||||
export interface VibePlan extends VibePlanVersion {
|
||||
items: VibePlanItem[];
|
||||
}
|
||||
|
||||
/** Derived session-director memory. The ledger remains authoritative; this
|
||||
* compact row makes fingerprints, bounded goals, and exploration state cheap
|
||||
* to read while planning. */
|
||||
export interface VibeSessionProfile {
|
||||
session_id: string;
|
||||
user_id: string;
|
||||
fingerprint: Record<string, unknown>;
|
||||
goals: Record<string, unknown>;
|
||||
exploration_coefficient: number;
|
||||
discovery_radius: number;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user