personas: elevate to server with per-persona fact scoping
Move personas off the mobile client into ImageApi as first-class records, and scope entity_facts by persona so each one builds its own voice over a shared entity graph. The new include_all_memories flag lets a persona opt back into the full hive-mind pool for human browsing of /knowledge/*; agentic generation always stays in-voice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
-- Drop the persona-scoping column on entity_facts via the table-rebuild
|
||||
-- dance for SQLite-version portability (matches the pattern in
|
||||
-- 2026-04-20-000000_add_backend_to_insights/down.sql).
|
||||
DROP INDEX IF EXISTS idx_entity_facts_persona;
|
||||
|
||||
CREATE TABLE entity_facts_backup AS
|
||||
SELECT id, subject_entity_id, predicate, object_entity_id, object_value,
|
||||
source_photo, source_insight_id, confidence, status, created_at
|
||||
FROM entity_facts;
|
||||
|
||||
DROP TABLE entity_facts;
|
||||
|
||||
CREATE TABLE entity_facts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
subject_entity_id INTEGER NOT NULL,
|
||||
predicate TEXT NOT NULL,
|
||||
object_entity_id INTEGER,
|
||||
object_value TEXT,
|
||||
source_photo TEXT,
|
||||
source_insight_id INTEGER,
|
||||
confidence REAL NOT NULL DEFAULT 0.6,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at BIGINT NOT NULL,
|
||||
CONSTRAINT fk_ef_subject FOREIGN KEY (subject_entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_ef_object FOREIGN KEY (object_entity_id) REFERENCES entities(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_ef_insight FOREIGN KEY (source_insight_id) REFERENCES photo_insights(id) ON DELETE SET NULL,
|
||||
CHECK (object_entity_id IS NOT NULL OR object_value IS NOT NULL)
|
||||
);
|
||||
|
||||
INSERT INTO entity_facts
|
||||
SELECT id, subject_entity_id, predicate, object_entity_id, object_value,
|
||||
source_photo, source_insight_id, confidence, status, created_at
|
||||
FROM entity_facts_backup;
|
||||
|
||||
DROP TABLE entity_facts_backup;
|
||||
|
||||
CREATE INDEX idx_entity_facts_subject ON entity_facts(subject_entity_id);
|
||||
CREATE INDEX idx_entity_facts_predicate ON entity_facts(predicate);
|
||||
CREATE INDEX idx_entity_facts_status ON entity_facts(status);
|
||||
CREATE INDEX idx_entity_facts_source_photo ON entity_facts(source_photo);
|
||||
|
||||
DROP INDEX IF EXISTS idx_personas_user;
|
||||
DROP TABLE IF EXISTS personas;
|
||||
@@ -0,0 +1,64 @@
|
||||
-- Personas live server-side now (mobile previously stored them in
|
||||
-- AsyncStorage only). Each user gets the three built-ins seeded; custom
|
||||
-- personas land here too via POST /personas or POST /personas/migrate.
|
||||
--
|
||||
-- `entity_facts` gains a persona_id so each persona accumulates its own
|
||||
-- voice over a shared entity graph (entities themselves stay unscoped).
|
||||
-- Existing rows backfill to 'default' via the column DEFAULT — that
|
||||
-- becomes the historical baseline. The `include_all_memories` flag on
|
||||
-- personas lets any persona opt back into reading the full pool.
|
||||
|
||||
CREATE TABLE personas (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
persona_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
system_prompt TEXT NOT NULL,
|
||||
is_built_in BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
include_all_memories BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at BIGINT NOT NULL,
|
||||
updated_at BIGINT NOT NULL,
|
||||
UNIQUE(user_id, persona_id),
|
||||
CONSTRAINT fk_personas_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_personas_user ON personas(user_id);
|
||||
|
||||
-- Seed built-ins for every existing user. System prompts copied verbatim
|
||||
-- from FileViewer-React/hooks/usePersonas.tsx so server and client agree
|
||||
-- on the canonical voice for each built-in.
|
||||
INSERT INTO personas (user_id, persona_id, name, system_prompt, is_built_in, created_at, updated_at)
|
||||
SELECT
|
||||
u.id,
|
||||
'default',
|
||||
'Default Assistant',
|
||||
'You are my long-term memory assistant. Use only the information provided. Do not invent details. Respond in 3–6 sentences in third person, leading with the most concrete moment from the photo and the surrounding context. Plain prose, no headings.',
|
||||
TRUE,
|
||||
strftime('%s', 'now') * 1000,
|
||||
strftime('%s', 'now') * 1000
|
||||
FROM users u
|
||||
UNION ALL
|
||||
SELECT
|
||||
u.id,
|
||||
'journal',
|
||||
'Personal Journal',
|
||||
'You are a personal journal writer. Write in first person, present tense, with warmth and reflection — focusing on emotions and meaningful moments. Use only the information provided; do not invent details. Aim for 4–8 sentences in a single flowing paragraph, no headings.',
|
||||
TRUE,
|
||||
strftime('%s', 'now') * 1000,
|
||||
strftime('%s', 'now') * 1000
|
||||
FROM users u
|
||||
UNION ALL
|
||||
SELECT
|
||||
u.id,
|
||||
'factual',
|
||||
'Factual Reporter',
|
||||
'You are a factual memory recorder. Be precise, objective, and concise. Lead with the date and place, then list what / when / who in 2–4 short sentences. Use only the information provided; if a detail is unknown, say so rather than guessing.',
|
||||
TRUE,
|
||||
strftime('%s', 'now') * 1000,
|
||||
strftime('%s', 'now') * 1000
|
||||
FROM users u;
|
||||
|
||||
-- Persona scoping on facts only. Entities and entity_photo_links stay
|
||||
-- shared (real-world referents and shared photo ↔ entity associations).
|
||||
ALTER TABLE entity_facts ADD COLUMN persona_id TEXT NOT NULL DEFAULT 'default';
|
||||
CREATE INDEX idx_entity_facts_persona ON entity_facts(persona_id);
|
||||
Reference in New Issue
Block a user